Expression

From Dyna
Jump to: navigation, search

An expression is a term with an associated value. There are three types of expressions, described below. See also semi-expression.

Contents

Primitive expressions

Any primitive term is an expression whose value is itself:

3
"foo"

Items

Any item is an expression.

lambda 
constit("np",3,8)

Remember that a structure type is an item type if it has been declared as such:

:- item(lambda, double, 0).
:- item(constit, boolean, false).

Derived-item expressions

If x is an item, then ?x is a boolean-valued expression that is true iff the item x has been derived. This means either that x has been asserted, or that x is the consequent of at least one inference rule whose antecedent items were all derived.

This is especially useful in conditional expressions . For example, ?edge("u","v") tells you whether a particular edge exists, whereas edge("u","v")!=0 will treat 0-weight edges as nonexistent.

The ? operator incurs some extra overhead. If you use ? with items of type foo, then Dyna must distinguish underived items from items that were asserted, or otherwise derived, with the default value. For example, it cannot get rid of an item (during retraction, say) simply by setting its value to the default.

Computed expressions

Computed expressions work as in languages like C++. Examples:

log(lambda)
lambda*constit("np",3,8)
1 / (1 + exp(-weight(node22)))

A structure type is a computed expression type if it has been declared as such. This means that its functor has been declared to be a function:

% All three of these declarations are actually superfluous since Dyna
% automatically declares the C++ operators, and log, as functions.  
:- computed(log).
:- computed(*, 'operator*').    % second argument is the name of the C++ function to call
:- computed(/, 'operator/').    

Notice that a term whose functor is * will have two subterms such as lambda and constit("np",3,8). The subterms themselves are not numeric values but full-fledged expressions in their own right union. The value of the term is computed by multiplying the values of the two subterms.

What if * is a union over several subtypes: can these have different value types (operator overloading), and can some of them not be computed at all (* as pairing term)?

Current limitations of computed expressions

At present, the :- computed declaration is not implemented, so you can't import your own functions yet. Indeed, the only functions currently allowed are *, +, and &, and only one of them can be used in a given program. For details, see inference rule and current limitations and workarounds.

The "whenever" syntax in inference rules acts like an if/then operator. Actually, since these can be returned as antecedents, I think we need to regard them as expressions, but they're funny expressions since they have no value if the "if" clause fails. So it can't be legal to evaluate them outside the context of an inference rule? Or they can just return null_term as a value?

Logarithmic literals

The current implementation of Dyna also recognizes a special syntax for literal doubles:

log(0.5)   % equivalent to the double 0.69314718

Once Dyna lets you call arbitrary functions; we will be able to remove this special syntax without damaging the meaning of log(0.5).

Value type of computed expressions

In general, the typed value of a computed expression is determined by calling the C++ function corresponding to the expression's functor on the values of the expression's arguments. The function must be appropriately declared in C++, but I think it may be unnecessary to repeat that declaration in Dyna. The Dyna compiler simply generates C++ code that contains the appropriate function call. The C++ compiler is then responsible for resolving the function (if the name is overloaded), and may produce an error if no appropriate function has been declared in C++.

I see the following reasons why Dyna might want to know type information about functions. Perhaps such declarations can be optional. (1) type checking of arguments to the function (with overload resolution), but we can pass that off to C++ as noted above. (2) maybe the generated code needs temporary variables that hold the value of a computed expression, so the generator needs to know the return type. Fortunately we can pass that off to C++ too: at least gcc gives us a typeof construct that we can use for that purpose, and maybe we can generate code without temporaries anyway. (3) we don't know the value types of expressions on the C++ side. For example, c[hello*world] can't work because we can't declare a return type for operator[] when applied to the hello*world expression. Nor can we use the reflection API to get the valtype of that expression. We can't store it in an expr_double variable, just in a times or expr variable. (4) we won't know the type of something created by the =subterm construction, which means: (4a) declaration inference gets no information from =subterm instances and may therefore get too narrow or too broad an answer. (4b) in something like foo(=hello*world), we can't split the parent type into different kinds of foo at compile time.
It wouldn't be too much of a burden to ask the user to declare a return type, or to use one if declared. The trouble is that the return type may depend on the argument types in case of overloading, so we also have to declare the argument types. In short, we get into the business of redeclaring all the C++ prototypes in Dyna.
Ideally this would be handled automatically by declaration inference. Could dynac manage to extract the information from the C++ source program? (SWIG may contain code that can help for this: see the %include syntax under "SWIG for the truly lazy." Or maybe there is a C++ yacc grammar out there: see [1] for a possibility, but also [2] for earlier skepticism.) Or run gcc on a test program and extract the information from the compiled symbol tables or debugger information?
The user is free to use macros in place of functions, but they'd have to be declared: we can't expect declaration inference to figure them out. (But probably smarter to use inlined functions, preferably defined in header files so that separate compilation doesn't prevent them from being inlined.)
Personal tools
Namespaces

Variants
Actions
Navigation
Tools