Skip to content
timrdf edited this page Mar 10, 2012 · 18 revisions

The control: library contains primitives dealing with program flow and the application of mappings.

Basic application

These primitives are variations on the theme of application of mappings. In Ripple, every object you can name has an associated mapping which, when applied to a stack, transforms it into zero or more new stacks (known as the solutions of the given mapping for the given stack).

Two of these primitives (i.e. dip and dipd) are inherited from Joy, having been adapted to Ripple semantics. The apply primitive is roughly equivalent to Joy's i combinator.

apply

This is the simplest primitive which applies mapping semantics. All of the other primitives in this library, apart from ary and inverse, are variations on apply. It expects one argument at the top of the stack, which is interpreted as a program to be executed. Depending on the type of the argument, it is executed in different, conceptually equivalent ways:

  • if it is a list, it is dequoted. E.g. (1 2 3) becomes 1 2 3.
  • if it is a primitive, it is applied to the rest of the stack according to the internal rules of the primitive
  • if it is a URI, it is treated as an RDF predicate, and solutions are derived from RDF statements
  • if it is a string, it is treated as a "key", and solutions are derived from key/value pairs

Other, internal types have their own semantics, while some types have no defined semantics and default to a null mapping (i.e. a mapping which never produces any solutions).

This primitive pops its argument from the stack, then determines what type of mapping it represents, as in the list above. Using the well-defined arity of the mapping, Ripple reduces the remainder of the stack until there are a corresponding number of inactive items at the top of the stack. These are then popped from the stack and passed into the mapping as its arguments, and the resulting solutions are then produced.

Note that the expression apply. or apply op has the same solution space as the expression . or op; the difference is that apply can be used as an inactive element in programs (i.e. delaying evaluation), whereas op forces evaluation wherever it appears. The apply primitive may also be used to make an application step more obvious than an extra ..

Examples:

1)  (1 2 3) apply.

  [1]  1 2 3

2)  42 dup apply.

  [1]  42 42

3)  <http://identi.ca/user/114> foaf:knows apply.

  [1]  <http://identi.ca/user/1>
  [2]  <http://identi.ca/user/58>
  [3]  <http://identi.ca/user/58>
  [...]

4)  <http://identi.ca/user/114> (foaf:name bio:olb) each. apply.

  [1]  "Danny Ayers"
  [2]  "semweb fan, works for  Talis"

dip

This primitive is inherited from Joy, and applies the mapping immediately below the topmost item on the stack. It expects two arguments on the top of the stack. It pops the arguments from the stack, retains the first argument (the second-to-topmost item on the stack) and applies the mapping associated with the second argument to the rest of the stack. It then pushes the first argument onto each solution.

This primitive is often used in combination with ary (see above), in order to force the application of the mapping, which would otherwise not be applied for an end solution as it's not at the top of the stack.

Examples:

1)   1 2 dup dip.

  [1]  1 dup. 2

2)   1 2 dup dip. 2 ary.

  [1]  1 1 2

dipd

This primitive behaves identically to dip (see above), except that it expects three arguments (of which the mapping to be applied is the top-most) and places the mapping two levels deep into the stack.

Examples:

1)  1 2 3 dup dip.

  [1]  1 2 dup. 3

2)  1 2 3 dup dip. 2 ary.

  [1]  1 2 2 3

Arity and inverses

Every mapping in Ripple has a well-defined arity (an integer value indicating how many arguments it requires) and inverse (another mapping whose solutions reverse the action of the original mapping).

ary

This primitive forces evaluation of the stack to a given depth. Essentially, it applies an identity mapping with a given arity. For example, the program -2 abs. 3 is fully reduced, according to Ripple's default lazy evaluation, in which solutions are required only to have an inactive item (such as 3 and unlike the application operator represented by .) at the top of the stack. It does not matter that the stack can be further simplified by applying the abs primitive; until some mapping requires the item below 3 in the stack, it will not be computed. However, the expression -2 abs. 3 add. will force abs to be applied, because add requires two arguments. This is the action of the ary primitive, which has no other effect on the stack. It expects a single argument on the top of the stack, which is the arity of the identity mapping to be applied. It causes the stack to be reduced to so many levels, and produces the resulting stacks as solutions.

This primitive is often used in recursive functions, and at the command line when it is a human who needs to see items further down in the stack.

Examples:

1)  -2 abs. 3 2 ary.

  [1]  2 3

2)  # Without the "ary", this would never terminate
3)  @list each: rdf:first (rdf:rest. :each.) both. 2 ary. apply.
3)  
4)  (1 2 3) :each.

  [1]  3
  [2]  2
  [3]  1

inverse

This primitive finds the inverse of a Ripple mapping. For example, the inverse of the add primitive is sub. The inverse of sin (the sine function) is asin (the arcsine function). The inverse of an RDF predicate mapping, which normally maps subjects to objects of RDF statements, maps objects to subjects. Not all mappings have a non-trivial inverse. For example, the inverse of the pop primitive, which pops an item from the top of the stack and discards it, is conceptually an operation which pushes something... anything... to the stack so that a subsequent call to pop will yield the original stack. As this is not a useful operation, the inverse of pop is a null mapping, which simply produces no stacks. When an inverse mapping is specially defined, its inverse should be the original mapping. Ripple includes syntactic sugar for inverse: the tilde symbol ~ is defined as inverse op.

This primitive expects a single argument at the top of the stack: the mapping to invert. It pops the mapping from the stack and pushes the inverse mapping. That mapping must then be applied to take effect; until then, it is just another inactive item on the stack.

Examples:

1)  42 6 mul.

  [1]  252

2)  42 6 mul~.

  [1]  7

3)  true false and.

  [1]  false

4)  false and~.

  [1]  true false
  [2]  false true
  [3]  false false

5)  rdf:type rdfs:label.

  [1]  "type"

6)  "type" rdfs:label~.

  [1]  rdf:type

Regular expressions

These primitives support POSIX-style regular expressions for abstract traversal paths.

option-apply

This primitive applies a mapping "zero or one" times. It is used in connection with the ? construct in Ripple's regular expression syntax. It expects one argument on the top of the stack: a mapping. It pops the mapping from the stack and pushes a wrapper for the mapping which has the following behavior. When applied, the wrapper produces the remaining stack as a solution. In parallel, it applies the mapping and produces any solutions.

Examples:

1)  1 (10 add.) option-apply. apply.

  [1]  1
  [2]  11

2)  1 (10 add.)?               

  [1]  1
  [2]  11

3)  <http://identi.ca/user/114> foaf:name.            

  [1]  "Danny Ayers"

4)  <http://identi.ca/user/114> foaf:knows? foaf:name.

  [1]  "Danny Ayers"
  [2]  "Jim Hughes"
  [3]  "Evan Prodromou"
  [4]  "Brian Manley"
  [...]

plus-apply

This primitive applies a mapping "one or more" times. It is used in connection with the + construct in Ripple's regular expression syntax. It expects one argument on the top of the stack: a mapping. It pops the mapping from the stack and pushes a wrapper for the mapping which, when applied, generates intermediate solutions as follows. The original stack is considered to be an intermediate solution of order 0. When the mapping is applied to intermediate solutions of order 0, the resulting intermediate solutions are of order 1, and so on. The plus-apply wrapper produces intermediate solutions of order 1 and higher as end solutions.

Examples:

1)  (1 2 3) rdf:rest plus-apply. apply.  rdf:first.

  [1]  2
  [2]  3

2)  (1 2 3) rdf:rest+ rdf:first.              

  [1]  2
  [2]  3

3)  <http://identi.ca/user/114> foaf:knows+

  [1]  <http://identi.ca/user/1>
  [3]  <http://identi.ca/user/58>
  [4]  <http://identi.ca/user/226>

range-apply

This primitive applies a mapping "from n to m" times. It is used in connection with the {n, m} construct in Ripple's regular expression syntax. It expects three argument on the top of the stack: a mapping, a "min" value n, and a "max" value m, where m >= n. It pops the arguments from the stack and pushes a wrapper for the mapping which, when applied, generates intermediate solutions as follows. The original stack is considered to be an intermediate solution of order 0. When the mapping is applied to intermediate solutions of order 0, the resulting intermediate solutions are of order 1, and so on. The range-apply wrapper produces intermediate solutions of order n through m (inclusive) as end solutions.

Examples:

1)  (1 2 3 4 5) rdf:rest 1 3 range-apply. apply.

  [1]  (2 3 4 5)
  [2]  (3 4 5)
  [3]  (4 5)

2)  (1 2 3 4 5) rdf:rest{1, 3}             

  [1]  (2 3 4 5)
  [2]  (3 4 5)
  [3]  (4 5)

star-apply

This primitive applies a mapping "zero or more" times. It is used in connection with the * construct in Ripple's regular expression syntax. It expects one argument on the top of the stack: a mapping. It pops the mapping from the stack and pushes a wrapper for the mapping which, when applied, generates intermediate solutions as follows. The original stack is considered to be an intermediate solution of order 0. When the mapping is applied to intermediate solutions of order 0, the resulting intermediate solutions are of order 1, and so on. The star-apply wrapper produces intermediate solutions of order 0 and higher (i.e. all intermediate solutions of any order) as end solutions.

Examples:

1)  (1 2 3) rdf:rest star-apply. apply.

  [1]  (1 2 3)
  [2]  (2 3)
  [3]  (3)
  [4]  ()

2)  (1 2 3) rdf:rest*             

  [1]  (1 2 3)
  [2]  (2 3)
  [3]  (3)
  [4]  ()

times-apply

This primitive applies a mapping "n" times. It is used in connection with the {n} construct in Ripple's regular expression syntax. It expects two argument on the top of the stack: a mapping and a value n. It pops the arguments from the stack and pushes a wrapper for the mapping which, when applied, generates intermediate solutions as follows. The original stack is considered to be an intermediate solution of order 0. When the mapping is applied to intermediate solutions of order 0, the resulting intermediate solutions are of order 1, and so on. The times-apply wrapper produces intermediate solutions of order n as end solutions.

Examples:

1)  (1 2 3 4 5) rdf:rest 3 times-apply. apply. 

  [1]  (4 5)

2)  (1 2 3 4 5) rdf:rest{3}                   

  [1]  (4 5)

Recursive mappings on lists

These primitives apply mappings sequentially to each element in a list. They are inherited from Joy, having been adapted to Ripple semantics.

fold

This primitive aggregates a list by applying a mapping sequentially to the items in the list. For example, it can be used to find the sum of the numbers in a list, or to execute a domain-specific program in sequence. It expects three arguments at the top of the stack: the list to fold, an initial value which serves as a "starting point" for the fold, and a mapping which generally consumes two arguments and produces one item at the top of the stack. It pops the arguments from the stack, then pushes the initial value and the first element in the list back to the stack. For each item in the list, it pushes the item to the stack, then applies the mapping. Each time the mapping is applied, it leaves a new item at the top of the stack, which serves as the initial value for the next application of the mapping.

Examples:

1)  (1 2 3) 0 add fold.

  [1]  6

2)  (1 2 3) 100 add fold.

  [1]  106

3)  () 42 add fold.

  [1]  42

4)  @list l invert:  l () swons fold.
5)  (1 2 3) :invert.

  [1]  (3 2 1)

6)  @list v mappings preserve-path:  mappings v (dupd. apply.) fold.
7)  <http://identi.ca/user/114> (foaf:knows foaf:name) :preserve-path. distinct.          

  [1]  <http://identi.ca/user/114> <http://identi.ca/user/58> "kael"
  [2]  <http://identi.ca/user/114> <http://identi.ca/user/1> "Evan Prodromou"
  [3]  <http://identi.ca/user/114> <http://identi.ca/user/226> "Dan Brickley"
  [...]

map

This primitive applies a mapping to a list in an entry-wise fashion, producing a new list. It expects two arguments at the top of the stack: a list and a mapping. It pops the arguments from the stack, then pushes a new list which constructed by applying the mapping in turn to each item in the original list.

Examples:

1)  (1 2 3) neg map.

  [1]  (-1 -2 -3)

2)  (0 1 2) sqrt map.

  [1]  (0.0E0 1.0E0 1.4142135623730951E0)
  [2]  (0.0E0 -1.0E0 1.4142135623730951E0)
  [3]  (0.0E0 1.0E0 -1.4142135623730951E0)
  [4]  (0.0E0 -1.0E0 -1.4142135623730951E0)

Looping and branching

These primitives control program flow through the use of logical operations. They are inherited from the Joy programming language and adapted to the streams-of-stacks model.

branch

This primitive dequotes (executes) one of two programs, depending on a given boolean value. It expects a stack with three arguments on top: the boolean decision value, the "if true" program t, and the "if false" program f. It pops these arguments from the stack, then pushes p followed by op, where p is the program t if the decision value is equal to true, otherwise the program f.

Example:

1)  @list i random-step: i 1 random. 0.5 gt. add sub branch.
2)  0 :random-step.

  [1]  -1

choice

This primitive produces one of two values, depending on a given boolean value. It is similar to branch except that it does not execute the chosen value, but only pushes it to the stack. It expects a stack with three arguments on top: the boolean decision value, the "if true" value t, and the "if false" value f. It pops these arguments from the stack, then pushes t or f, depending on the decision value.

Example:

1)  "this millisecond is " time. 2 mod. 0 equal. "even" "odd" choice. concat.

  [1]  "this millisecond is even"

ifte

This primitive is an "if-then-else" construct which is similar to branch, except that instead of expecting a boolean decision value on the stack, it expects a decision program, which is executed to produce a boolean value. It expects three arguments at the top of the stack: the decision program d, the "if true" program t, and the "if false" program f. It pops f and t from the stack, then activates (executes) d. For each resulting stack, it pops the topmost item from the stack, then pushes t if that item is equal to true, otherwise f.

Example:

1)  @list p knows: p foaf:knows. \
1)      (foaf:knows. p equal.) (foaf:name. " (mutually)" concat.) foaf:name ifte. \
1)      distinct.
2)
3)  <http://identi.ca/user/114> :knows.

  [1]  "Evan Prodromou (mutually)"
  [2]  "Brian Manley (mutually)"
  [3]  "Dan Brickley (mutually)"
  [...]
  [52]  "Ross Singer"
  [53]  "Jon Phipps"
  [54]  "Gautier Poupeau"
  [...]

require

This primitive enforces some boolean criterion to solutions accepted into the stream. It expects a single argument at the top of the stack: a mapping which, when applied to the rest of the stack, should yield a boolean value (true or false). It pops the mapping from the stack, then applies it to the rest of the stack to produce a stream of intermediate solutions. If at least one of those intermediate solutions contains a true at the top of the stack, then the rest of the original stack (i.e. without the mapping) is produced as a top-level solution.

Example:

1)  <http://identi.ca/user/114> foaf:knows. foaf:name. distinct. ("J" starts-with.) require.

  [1]  "Jim Hughes"
  [2]  "John Goodwin"
  [3]  "Jon Phipps"
  [4]  "Jonas Halvorsen"
  [5]  "Jaakko Rajaniemi"

while

This primitive executes a program as long as that program produces the value true. It expects two argument at the top of the stack: a decision program d and a primary program p. It pops the arguments from the stack, then pushes d back onto the stack and activates (executes) it. For each resulting stack, it pops the topmost value from the stack. If that value is not equal to true, it produces the stack as-is. Otherwise, it pushes and activates p, then repeats the process (the next step being to push and activate d).

Example:

1)  # First power of 2 greater than 1000
2)  1000 1 gt (2 mul.) while. popd.

  [1]  1024