Combinator stack effects
Factor handbook » The language » Stack effect checking

Prev:Straight-line stack effects
Next:Recursive combinator stack effects


If a word calls a combinator, one of the following conditions must hold for the stack checker to succeed:
• The combinator must be called with a quotation that is either literal or built from literal quotations, curry, and compose. (Note that quotations that use fry or locals use curry and compose from the perspective of the stack checker.)
• If the word is declared inline, the combinator may additionally be called on one of the word's input parameters or with quotations built from the word's input parameters, literal quotations, curry, and compose. When inline, a word is itself considered to be a combinator, and its callers must in turn satisfy these conditions.
• In a non-inline word with a fixed stack effect, a quotation input with a monomorphic effect annotation can be called. The compiler inserts a checked static call, including when the parameter is passed through locals, curry or compose. This supplies a call contract, not a literal value for arbitrary macro expansion.

If none of these conditions holds, the stack checker throws an unknown-macro-input or bad-macro-input error. To make the code compile, a runtime checking combinator such as call( must be used instead. See Stack effect checking escape hatches for details. An inline combinator can be called with an unknown quotation by currying the quotation onto a literal quotation that uses call(.

Input stack effects
Inline combinators will verify the stack effect of their input quotations if they are declared in the combinator's stack effect. See Stack effect row variables for details.

Examples

Calling a combinator
The following usage of map passes the stack checker, because the quotation is the result of curry:
USING: math sequences ; [ [ + ] curry map ] infer.
( x x -- x )

The equivalent code using fry and locals likewise passes the stack checker:
USING: fry math sequences ; [ '[ _ + ] map ] infer.
( x x -- x )

USING: locals math sequences ; [| a | [ a + ] map ] infer.
( x x -- x )


Defining an inline combinator
The following word calls a quotation twice; the word is declared inline, since it invokes call on the result of compose on an input parameter:
: twice ( value quot -- result ) dup compose call ; inline

The following code now passes the stack checker; it would fail were twice not declared inline:
USE: math.functions [ [ sqrt ] twice ] infer.
( x -- x )


Defining a combinator for unknown quotations
A fixed quotation input annotation lets a non-inline word use call:
: apply-one ( x quot: ( x -- y ) -- y ) call ;

This compiles the call with the same runtime checking as call( x -- y ). Row-polymorphic quotation parameters still require inlining.

In the next example, call( must be used because the quotation is the result of calling a runtime accessor, and the compiler cannot make any static assumptions about this quotation at all:
TUPLE: action name quot ; : perform ( value action -- result ) quot>> call( value -- result ) ;


Passing an unknown quotation to an inline combinator
Suppose we want to write:
: perform ( values action -- results ) quot>> map ;

However this fails to pass the stack checker since there is no guarantee the quotation has the right stack effect for map. It can be wrapped in a new quotation with a declaration:
: perform ( values action -- results ) quot>> [ call( value -- result ) ] curry map ;


Explanation
This restriction exists because without further information, one cannot say what the stack effect of call is; it depends on the given quotation. If the stack checker encounters a call without further information, a unknown-macro-input or bad-macro-input error is raised.

On the other hand, the stack effect of applying call to a literal quotation or a curry of a literal quotation is easy to compute; it behaves as if the quotation was substituted at that point.

Passing quotations through recursive combinators
A known quotation remains known after an inline recursive combinator such as each or map returns, provided its body never accesses that value in the caller's stack prefix:
[ [ reverse ] swap [ reverse ] map swap call ] infer.
( x -- x )

The quotation can also be held with dip:
[ [ reverse ] [ [ reverse ] map ] dip call ] infer.
( x -- x )