Skip to content

Latest commit

 

History

History
546 lines (444 loc) · 10.9 KB

09-primitives-builtins-and-prelude.md

File metadata and controls

546 lines (444 loc) · 10.9 KB

→ README, → prev: Syntactic Sugar, → next: Standard Library

Primitives, Builtins & Prelude

→ Primitives, → Builtins, → Prelude

Primitives

Primitive operations that make up the core language.

NB: all primitives start and end with __ (e.g. __call__); the prelude defines aliases without them (for the primitives intended to be used directly); unlike the primitives themselves, these aliases (e.g. call) can be shadowed by named parameters and module definitions.

Calling, Branching & Comparing

>>> 41 [ 1 + ]                  ; (put a block on the stack)
[ 1 + ]
>>> call                        ; call the callable on top of the stack
42

>>> , :T [ :truthy ] def, :F [ :falsy ] def
>>> #t  [ T ] [ F ] if          ; conditional/branch
:truthy
>>> #f  [ T ] [ F ] if          ; false is falsy
:falsy
>>> nil [ T ] [ F ] if          ; so is nil
:falsy
>>> 42  [ T ] [ F ] if          ; everything else is truthy
:truthy
>>> 0 'T 'F if                  ; "branches" need not be blocks
:truthy

>>> 1 2 <                       ; compare: = not= < <= > >=
#t
>>> 1 2 <=>                     ; compare: -1 if <, 0 if =, 1 if >
-1
CAVEATS

The comparison functions =, <, <=>, etc. define an almost total ordering on all types. This makes sorting heterogeneous lists easy.

The exceptions are that blocks, builtins, multis, and thunks are ordered respective to all other types but cannot be compared to values of their own type and the "usual" behaviour of NaN being unequal to even itself.

NB: the ordering also treats all types as completely distinct -- including numeric types like int and float.

>>> ( "foo" ( :bar ) nil ) sort ; uses <=> :-)
( nil "foo" ( :bar ) )
>>> ( 1 2.0 3 4.0 ) sort        ; uses <=> :-(
( 1 3 2.0 4.0 )

The "alternative comparison" functions eq, lt, cmp, etc. define a partial ordering. They compare mixed int and float (by converting int->float) and reject all other mixed type comparisons.

>>> 42 :foo <=>
-1
>>> 42 :foo cmp
*** ERROR: types int and kwd are not comparable

>>> 2 1.0 <=>                   ; also: = not= < <= > >=
-1
>>> 2 1.0 cmp                   ; also: eq neq lt lte gt gte
1

>>> ( 1 2.0 3 4.0 ) sort'       ; uses cmp :-)
( 1 2.0 3 4.0 )

Conversion, Type Information & I/O

>>> ( 42 ) show                 ; convert to readable str
"( 42 )"
>>> "foo" show
"\"foo\""

>>> -2 abs
2
>>> -3.14 abs
3.14

>>> 3.14 trunc
3
>>> 9.5 round
10
>>> -9.5 round
-10
>>> 10.5 round
10
>>> -10.5 round
-10
>>> 0.0 0.0 / round             ; NaN or Infinite -> nil
nil
>>> -3.14 floor
-4
>>> -3.14 ceil
-3

>>> 1 int->float
1.0

>>> () type                     ; get type as keyword
:list
>>> 1 type
:int
>>> type
:kwd

>>> 1 callable?
#f
>>> 'if callable?
#t
>>> () callable?
#t
>>> () function?
#f

>>> , "Hello!" say!             ; print line
Hello!
>>> "What's your name? " ask!   ; print prompt and read line
What's your name? Foo
"Foo"

Module Information

>>> , :answer 42 def
>>> __name__
:__main__
>>> __module-defs__
( :D! :__args__ :__repl__ :answer :c! :clear-stack! :d! :s! :show-stack! )
>>> :answer :__main__ __module-get__
42
>>> __modules__ [ show ":__" starts-with? ] filter ->list
( :__bltn__ :__main__ :__prim__ :__prld__ )

REPL: Clear & Show Stack

>>> clear-stack!                ; only available in the repl
*** STACK CLEARED ***
>>> ,show-stack!
--- STACK ---
---  END  ---
>>> , 1 2 s!                    ; s! ⇔ show-stack!
--- STACK ---
2
1
---  END  ---

Exceptions

>>> "oops!" fail                ; raise exception
*** ERROR: oops!

>>> , [ :try 1 2 3 ... ]
...   [ t m i . :catch { type: 't, message: 'm, info: 'i } #t ]
...   [ :finally ] try s!       ; try/catch/finally
--- STACK ---
:finally
{ :info ( "__ellipsis__" ) =>, :message "name __ellipsis__ is not defined" =>, :type :NameError => }
:catch
---  END  ---
>>> c!
*** STACK CLEARED ***
>>> , [ :no :errors :this :time ] [ ... ] [ :cleanup :here ] try s!
--- STACK ---
:here
:cleanup
:time
:this
:errors
:no
---  END  ---
>>> , [ ... ] nil [ :finally ] try  ; w/o "catch"
*** ERROR: name __ellipsis__ is not defined
>>> , [ ... ] [ 3drop "ignoring error!" say! #t ] [] try
ignoring error!

NB: the catch block must return a value that indicates whether the exception has been handled; it will be re-raised if it is falsy. This can -- and should! -- be used to only catch certain types of errors.

>>> , [ 1 0 div ] [ 2drop :DivideByZero = ] [] try
>>> , [ ...     ] [ 2drop :DivideByZero = ] [] try
*** ERROR: name __ellipsis__ is not defined

Regexes

>>> "foo" "^f" rx-match         ; matched part
( "f" )
>>> "foo" "^f(o+)" rx-match     ; and groups
( "foo" "oo" )
>>> "bar" "^f" rx-match         ; or nil
nil

>>> "foo bar" "$2 $1" "(\w+) (\w+)" #f rx-sub       ; str or block
"bar foo"
>>> "1 2 3 4" [ swap " " ++sep++ nip ] "(\w+) (\w+)" #t rx-sub
"2 1 4 3"

>>> "foo 子猫 bar" [ reverse ] "\p{L}+" #t rx-sub   ; replace all
"oof 猫子 rab"
>>> "foo 子猫 bar" [ reverse ] "\p{L}+" #f rx-sub   ; replace first
"oof 子猫 bar"

Miscellaneous

>>> , [ 0.1 sleep :one ] [ 0.05 sleep :two ] par    ; concurrency
>>> ,s!
--- STACK ---
:two
:one
---  END  ---

>>> , [ [ 1 sleep "oops" fail ]
...     [ 1 [ D! inc 0.2 sleep #t ] loop ] par ]
...   [ 3list d! #t ] try-catch
1
2
3
4
5
( :Fail "oops" ( "oops" ) )
>>> __version__                       ; version & platform information
( ( 0 0 1 ) :hs ( "linux x86_64" "ghc 8.6" ) )

>>> __version__
( ( 0 0 1 ) :js-node ( "linux x64" "node v10.17.0" ) )

>>> __version__ 1st "v${0}.${1}.${2}" fmt
"v0.0.1"

Of course def and => are also primitives.

There are also primitive arithmetic operations, but the prelude defines more convenient versions of these, like + and div: __int+__, __int-__, __int*__, __div__, __mod__, __float+__, __float-__, __float*__, __float/__.

To list all primitives, run:

>>> :__prim__ __module-defs__   ; (elided)
( :__=__ :__apply__ :__call__ :__def__ :__if__ ... )

Builtins

Operations that are easier or more efficient to implement in the interpreter but could have been defined in the prelude instead.

>>> 42 nil?                       ; there is a predicate for each type
#f
>>> nil nil?
#t
>>> 42 int?
#t
>>> :foo kwd?
#t

>>> "0x20" str->int
32
>>> "3.14" str->float
3.14
>>> "foo" str->int
nil

Prelude

→ Stack Shuffling, → Combinators, → Logic & Arithmetic;
→ Strings, Chars, Nil, Numbers & Pairs, → Lists, Dicts, Ranges & Sequences, → Miscellaneous, → More

A small set of standard definitions that is available automatically in all modules.

See lib/prelude.knk for the complete prelude with definitions and examples:
→ Syntax Highlighted Source, → Function Index

NB: the following is just an overview (and neither complete nor completely up-to-date); see the links above for all current prelude functions.

Stack Shuffling

>>> , 1 2 s!
--- STACK ---
2
1
---  END  ---
>>> , swap s!                     ; swap top 2 values on stack
--- STACK ---
1
2
---  END  ---
>>> , dup s!                      ; dup(licate) top of stack
--- STACK ---
1
1
2
---  END  ---
>>> , drop s!                     ; drop (pop & discard) top of stack
--- STACK ---
1
2
---  END  ---

See also: rot>, <rot, 2dup, 2drop, 3drop, nip, over, 2over, over2.

Combinators

bi calls 2 functions on 1 value, bi$ 1 function on 2 values, etc.

>>> , 35 [ 2 + ] [ 7 + ] bi
>>> , s!
--- STACK ---
42
37
---  END  ---

>>> 3 '+ dip *                    ; pop x, call f, push x
237

>>> 41 1 '+ $ call                ; partial function application
42
>>> 41 [ 1 + ] call
42

>>> 2 [ 1 + ] [ 3 * ] @ call      ; function composition
9
>>> 2 [ 1 + 3 * ] call
9

See also: 2$, 3$, %, 2dip, 3dip, keep, 2keep, tri, bi$, tri$, bi~, tri~, bi*, 2bi, 2tri, 2bi$, 2bi~.

Logic & Arithmetic

>>> #f not
#t
>>> #f 1 or
1
>>> 1 nil and
nil

>>> #t 42 37 ?
42

>>> 19 [ 2 div ] [ 3 * 1 + ] 'even? ~?
58

See also: when, min, max.

>>> 1 2 +
3
>>> 1.0 2.0 /
0.5
>>> 8 3 div
2
>>> 5.0 neg
-5.0
>>> 37 odd?
#t

Strings, Chars, Nil, Numbers & Pairs

>>> 0x732b chr
"猫"
>>> ord
29483
>>> nil [ "<nil>" ] [ type show ] ~nil
"<nil>"
>>> ( 3 7 ) 'rest ~> 'first ~> [ 1 + ] ~>
8
>>> ( 7 3 ) ( 'rest 'first [ 1 + ] ) ~~>
4

See also: num?, ~neg, ~zero, ~pos, ^pair.

Lists, Dicts, Ranges & Sequences

>>> () empty?
#t
>>> ( :x :y :z ) len
3
>>> ( :a :b :c ) 1 get^
:b

>>> ( 1 2 ) head^
1
>>> ( 1 2 3 ) tail^
( 2 3 )
>>> () head
nil
>>> , ( 1 2 3 ) uncons^ s!
--- STACK ---
( 2 3 )
1
---  END  ---
>>> cons
( 1 2 3 )
>>> ( 3 4 ) 2 swap cons
( 2 3 4 )
>>> ( 1 2 ) ( 3 4 ) ++
( 1 2 3 4 )

>>> ( :one :two :three ) 1 has?
#t
>>> ( :one :two :three ) :two elem?
#t

>>> { x: 1, y: 2 } { x: 99 } update
{ :x 99 =>, :y 2 => }

>>> 10 15 [m-n] ->list            ; ranges
( 10 11 12 13 14 15 )

>>> "0123456789" 3 -3 [i-j)       ; slicing
"3456"
>>> () seq                        ; non-empty sequence or nil
nil
>>> ( 1 2 ) seq
( 1 2 )
>>> ( 1 2 ) first
1
>>> ( 1 2 ) rest
( 2 )

; branch/match on empty or first & rest
>>> [ "empty" ] [ x xt . "not empty" ] ^seq
"not empty"

>>> ( 2 3 4 ) [ dup * ] map ->list
( 4 9 16 )
>>> ( 2 3 4 ) [ 2 mod 0 = ] filter ->list
( 2 4 )
>>> ( 2 3 4 ) 10 '- foldl
1

; map etc. consume and produce "lazy" (and possibly infinite) sequences
>>> ( 1 2 3 ) cycle [ dup * ] map 7 take-first ->list
( 1 4 9 1 4 9 1 )

See also: [m-n), [0-), [1-n], etc.; 1list, lazy-seq, unseq, ^list, zip, foldr, concat, reverse, each, cycle, iterate, repeat, replicate, take-first, drop-first, take-while, drop-while, sort.

Miscellaneous

>>> , [ "Hi!" say! ] 3 times
Hi!
Hi!
Hi!

More

→ Syntax Highlighted Source, → Function Index