Skip to content

Latest commit

 

History

History
134 lines (108 loc) · 4.05 KB

05-functions.md

File metadata and controls

134 lines (108 loc) · 4.05 KB

→ README, → prev: Pairs, Lists & Dicts, → next: Multi(method)s

Functions

→ Blocks, → Calling vs Applying, → Functions vs Callables

Blocks

(almost, but not quite, entirely unlike lambdas)

A block consists of:

  • optional parameters (for which the arguments are popped from the stack -- right to left (i.e. from the top of the stack) -- when the block is called);
  • code (i.e. a sequence of tokens) that is executed when the block is called.

A block is delimited by square brackets; parameters (if any) are separated from the code by a ..

; push a block -- that pushes 42 onto the stack -- onto the stack
>>> [ 42 ]
[ 42 ]
>>> call                          ; call the block on the stack
42

>>> , :myblock [ 42 ] def         ; let's give it a name
>>> 'myblock                      ; put it on the stack
[ 42 ]
>>> call
42
>>> myblock                       ; call it by name
42
>>> , :myswap [ x y . 'y 'x ] def ; a block with named parameters
>>> 1 2 myswap
1
>>> ,s!
--- STACK ---
1
2
---  END  ---

Calling vs Applying

NB: since there are usually no guarantees about whether a block has named parameters (or how many), only blocks/functions known to explicitly support applying should be applied. Record constructors always support application.

Applying a normal block isn't much different from calling it. Except that it takes its arguments from the list it is applied to (in reverse order) instead of from the stack. The number of elements of the list (i.e. the number of arguments) must equal the number of parameters of the block. The block cannot (indirectly) access the part of the stack before the list it is applied to. It can push any number of return values on to the stack as usual.

>>> , :foo [ x y . ( 'x 'y ) show say! ] def  ; normal block
>>> :x :y foo                                 ; normal call
( :x :y )
>>> ( :x :y ) 'foo apply                      ; apply
( :x :y )
>>> foo( :x :y )                              ; sugar
( :x :y )

A block with a parameter named & will ignore that parameter when called normally (setting it to nil). It can be applied to a list with a number of elements equal to or greater than the number of normal parameters of the block. The parameter & is set to a (possibly empty) list of the remaining elements/arguments.

>>> , :foo [ x & . ( 'x '& ) show say! ] def  ; block with &
>>> :x foo                                    ; normal call
( :x nil )
>>> ( :x :y :z ) 'foo apply                   ; apply
( :x ( :y :z ) )
>>> foo( :x :y :z )                           ; sugar
( :x ( :y :z ) )

A block with a parameter named && will ignore that parameter when called normally (setting it to nil). It can be applied to a dict, which needs to provide values for each normal parameter (by name). The parameter && is set to a (possibly empty) dict of the remaining key/value pairs (the ones not used to provide values for the normal parameters).

>>> , :foo [ x && . ( 'x '&& ) show say! ] def  ; block with &&
>>> :x foo                                      ; normal call
( :x nil )
>>> { :x 1 =>, :y 2 => } 'foo apply-dict        ; apply-dict
( 1 { :y 2 => } )
>>> foo{ x: 1, y: 2 }                           ; sugar
( 1 { :y 2 => } )

Some more examples:

>>> , :&+ [ x & . '& 'x '+ foldl ] def
>>> &+( 1 2 3 4 )
10

... TODO ...

Functions vs Callables

Functions are: blocks, builtins, multis, and record-types; these all behave like (stack-based) functions and can be called (and sometimes applyd and/or apply-dicted).

Callables are: all types that can be called: foremostly functions, but also other types (e.g. list and record) that are not functions but which implement certain primitive operations via calls.