Joylog is a logic language concept inspired by the concatenative/stack-based language Joy. I’m thinking about it now again as I think it matches the semantics of term-expressions.
The basic concepts are:
- It’s a stack language but prefix rather than postfix based
- |X at the start of an expression Y, where X is a single word, is looked up in the current root environment.
- If X is not defined, then the term-expression is left as is – it is not an error, it is not an undefined symbol, it is simply a non-reducible term which identifies with and reduces to only itself. There may be many such non-reducible terms which act as structured data types (or somewhat Prolog-like, but context-dependent, terms).
- If X is a list, at the moment the behaviour is just assumed to be ‘exact match lookup as for a word’. This may change.
- If X is defined, ie contains child items, the definition D of X (the expression to the right of that one word starting from the environment root) is prepended to the remaining expression R under the following rules:
- if D is an |all expression, then some form of parallel lazy evaluation takes place, with each element of the |all expanded in its own stack space. The result is a |all expression
- if R is an |all expression, then each element of D is prepended to each element of R
- each resulting branch is recursively evaluated, with |X terms expanded, and so on. However some lazy-evaluation mechanism is used to prevent infinite loops, and while each line must be expanded to its end to produce output, only as much output as required by the consuming operation (to the left) is produced
- If X is a built-in primitive operation O, then operation O is applied to remainder R to produce result R2, and R2. or as much of it as required by a consuming operation, is passed back. This may mean, eg, evaluating (and only partially) only the first term in an |all expression.
- Otherwise, if the first element of expression Y is not |, then Y is just (Y1 . Y2), Y1 is returned to the left, and Y2 is evaluated as for Y
- The semantics of ‘returning to the left’, and management of |all expressions, may require a heap rather than a stack for execution, as operations need to ‘return one value but keep
The hope is I can have a root expression such as
(parent |all (a b) (b c))
(ancestor |transitive-closure |parent)
(transitive-closure |all () (|compose-self)
where compose-self somehow does a relational self-composition operation on the remainder R (perhaps implemented by wrapping R as a subexpression, duplicating it, applying a two-place compose operator, etc).
There are several major differences to Joy semantics, I think, that complicate evaluation:
- undefined operations could cause problems if used as data types and then are defined later. Perhaps we need a null |data operation to explicitly mark these as data (with or without type/schema markup)
- prefix semantics is required for lazy evaluation, so we can’t just act directly on a stack
- lazy evaluation is required because operations can generate recursive and infinite sets/relations. We don’t want to do more work than we need.
- multi-branch evaluation is required – this makes operations a little like co-routines, and means the stack isn’t quite a stack
- some kind of ‘get’ operation is required to retrieve the definition of a word as a wrapped expression, without evaluating it
- some kind of partial evaluation operation for evaluating only one part of a wrapped expression
- some kind of ‘match’ operation which evaluates an expression only for the purpose of confirming a match
- operations which manipulate the environment itself, and create temporary sub-environments which can be returned as data to other operations