From 6762f9cf876bd80bedb446ce121151be73eb0113 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 7 Sep 2018 17:53:08 -0400 Subject: [PATCH] Update Introduction. --- Introduction.md | 112 +++++++++++++++++++++++-- The-Janet-Abstract-Machine-Bytecode.md | 2 +- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/Introduction.md b/Introduction.md index 440fe48..37a458d 100644 --- a/Introduction.md +++ b/Introduction.md @@ -196,9 +196,16 @@ literals without binding them to a symbol. ``` # Evaluates to 40 ((fn [x y] (+ x x y)) 10 20) +# Also evaluates to 40 +((fn @[x y] (+ x x y)) 10 20) + +# Will throw an error about the wrong arity +((fn [x] x) 1 2) +# Will not throw an error about the wrong arity +((fn @[x] x) 1 2) ``` -The above expression first creates an anonymous function that adds twice +The first expression creates an anonymous function that adds twice the first argument to the second, and then calls that function with arguments 10 and 20. This will return (10 + 10 + 20) = 40. @@ -206,6 +213,9 @@ There is a common macro `defn` that can be used for creating functions and immed them to a name. `defn` works as expected at both the top level and inside another form. There is also the corresponding +Note that putting an ampersand in front of the argument list inhibits strict arity checking. +This means that such a function will accept fewer or more arguments than specified. + ```lisp (defn myfun [x y] (+ x x y)) @@ -365,6 +375,8 @@ itself, and the second parameter is the key. (get "hello, world" 0) # -> 104 ``` +### Destructuring + In many cases, however, you do not need the `get` function at all. Janet supports destructuring, which means both the `def` and `var` special forms can extract values from inside structures themselves. @@ -469,20 +481,110 @@ For any given function, use the `doc` macro to view the documentation for it in To see a list of all global functions in the repl, type the command ```lisp -(getproto *env*) +(table.getproto *env*) +# Or +(all-symbols) ``` Which will print out every built-in global binding (it will not show your global bindings). To print all -of your global bindings, just use *env*, which is a var +of your global bindings, just use \*env\*, which is a var that is bound to the current environment. +The convention of surrounding a symbol in stars is taken from lisp +and Clojure, and indicates a global dynamic variable rather than a normal +definition. To get the static environment at the time of compilation, use the +`_env` symbol. + # Prototypes -:) +To support basic generic programming, Janet tables support a prototype +table. A prototype table contains default values for a table if certain keys +are not found in the original table. This allows many similar tables to share +contents without duplicating memory. + +```lisp +# One of many Object Oriented schemes that can +# be implented in janet. +(def proto1 @{:type :my.custom1 + :behave (fn [self x] (print "behaving " x))}) +(def proto2 @{:type :my.custom2 + :behave (fn [self x] (print "behaving 2 " x))}) + +(def thing1 (table.setproto @{} proto1)) +(def thing2 (table.setproto @{} proto2)) + +(print (get thing1 :type)) # prints :my.custom1 +(print (get thing2 :type)) # prints :my.custom2 + +(defn behave [x y] + (let [{:behave f} x] + (f x y))) + +(behave thing1 :a) # prints "behaving :a" +(behave thing2 :b) # prints "behaving 2 :b" +``` + +Looking up in a table with a prototype can be summed up with the following algorithm. + +1. `(get my-table my-key)` is called. +2. my-table is checked for the key if my-key. If there is a value for the key, it is returned. +3. if there is a prototype table for my-table, set `my-table = my-table's prototype` and got to 2. +4. Return nil as the key was not found. + +Janet will check up to about a 1000 prototypes recursively by default before giving up and returning nil. This +is to prevent an infinite loop. This value can be changed by adjusting the `JANET_RECURSION_GUARD` value +in janet.h. + +Note that Janet prototypes are not as expressive as metatables in Lua and many other languages. +This is by design, as adding Lua or Python like capabilities would not be technically difficult. +Users should prefer plain data and functions that operate on them rather than mutable objects +with methods. # Fibers -:) +Janet has support for single-core asynchronous programming via coroutines, or fibers. +Fibers allow a process to stop and resume execution later, essentially enabling +multiple returns from a function. This allows many patterns such a schedules, generators, +iterators, live debugging, and robust error handling. Janet's error handling is actually built on +top of fibers (when an error is thrown, the parent fiber will handle the error). + +A temporary return from a fiber is called a yield, and can be invoked with the `yield` function. +To resume a fiber that has been yielded, use the `resume` function. When resume is called on a fiber, +it will only return when that fiber either returns, yields, throws an error, or otherwise emits +a signal. + +Different from traditional coroutines, Janet's fibers implement a signaling mechanism, which +is used to differentiate different kinds of returns. When a fiber yields or throws an error, +control is returned to the calling fiber. The parent fiber must then check what kind of state the +fiber is in to differentiate errors from return values from user defined signals. + +To create a fiber, user the `fiber.new` function. The fiber constructor take one or two arguments. +the first, necessary argument is the function that the fiber will execute. This function must accept +an arity of one. The next optional argument is a collection of flags checking what kinds of +signals to trap and return via `resume`. This is useful so +the programmer does not need to handle all different kinds of signals from a fiber. Any untrapped signals +are simply propagated to the next fiber. + +```lisp +(def f (fiber.new (fn @[] + (yield 1) + (yield 2) + (yield 3) + (yield 4) + 5))) + +# Get the status of the fiber (:alive, :dead, :debug, :new, :pending, or :user0-:user9) +(print (fiber.status f)) # -> :new + +(print (resume f)) # -> prints 1 +(print (resume f)) # -> prints 2 +(print (resume f)) # -> prints 3 +(print (resume f)) # -> prints 4 +(print (fiber.status f)) # -> print :pending +(print (resume f)) # -> prints 5 +(print (fiber.status f)) # -> print :dead +(print (resume f)) # -> throws an error because the fiber is dead +``` # Macros diff --git a/The-Janet-Abstract-Machine-Bytecode.md b/The-Janet-Abstract-Machine-Bytecode.md index fe11a4d..e97f956 100644 --- a/The-Janet-Abstract-Machine-Bytecode.md +++ b/The-Janet-Abstract-Machine-Bytecode.md @@ -190,7 +190,6 @@ failure to return or error. | `jmp` | `(jmp label)` | pc = label, pc += offset | | `jmpif` | `(jmpif cond label)` | if $cond pc = label else pc++ | | `jmpno` | `(jmpno cond label)` | if $cond pc++ else pc = label | -| `len` | `(len dest ds)` | $dest = length(ds) | | `ldc` | `(ldc dest index)` | $dest = constants[index] | | `ldf` | `(ldf dest)` | $dest = false | | `ldi` | `(ldi dest integer)` | $dest = integer | @@ -198,6 +197,7 @@ failure to return or error. | `lds` | `(lds dest)` | $dest = current closure (self) | | `ldt` | `(ldt dest)` | $dest = true | | `ldu` | `(ldu dest env index)` | $dest = envs[env][index] | +| `len` | `(len dest ds)` | $dest = length(ds) | | `lt` | `(lt dest lhs rhs)` | $dest = $lhs < $rhs | | `lti` | `(lti dest lhs rhs)` | $dest = $lhs \