JanetDocsSourcePlaygroundTutorialsI'm Feeling luckyCommunityGitHub sign in

Sogaiu has amazing material strown about his github account.

Some good articles

Def vs. Let

let

Overall, people seem to prefer def for everything, though experienced lispers will opt for let. Personally, I prefer let for 3+ declarations at the same time, but def for 1. With 2, they're equivalent (in chars etc.) Occasionally I like the style of doing all login within the let declarations themselves:

(let [a 1
      b 2
      c (+ a b)
      d (+ c 1)
      e (* 2 d)]
  e)

In reality, most of those steps would be applying some complex function (predefined or e.g. a network call); often they could be replaced with -> or a transducer (I have a WiP library for them.)

Here's a real example:

(defn github-auth [request]
  (let [code (get-in request [:query-string :code])
        result (http/post "https://github.com/login/oauth/access_token"
                          (string/format "client_id=%s&client_secret=%s&code=%s"
                                         (env :github-client-id)
                                         (env :github-client-secret)
                                         code)
                          :headers {"Accept" "application/json"})
        result (json/decode (result :body) true true)
        access-token (get result :access_token)
        auth-response (http/get "https://api.github.com/user"
                                :headers {"Authorization" (string "token " access-token)})
        auth-result (json/decode (auth-response :body) true true)]

    (var account (db/find-by :account :where {:login (auth-result :login)}))
    (unless account
      (set account (db/insert :account {:login (auth-result :login)
                                        :access-token access-token})))

    (db/update :account (account :id) {:access-token access-token})

    (-> (redirect-to :home/index)
        (put-in [:session :login] (account :login)))))

Binding Metadata

To mark deprication, add :depricated to def, var, defn or defmacro (after docstring, before param list). There are 3 lint levels which triger compiler warnings based on *lint-levels* and *lint-warn*:

For example, using the variable (def example :deprecated "example") you'll see: repl:2:1: compile warning (normal): normal-binding is deprecated. To set the levels: (def example {:depricated :relaxed} "example") You can set special warnings via the linting array (dyn :macro-lints).

:private

https://janet-lang.org/docs/functions.html#Optional-Flags

Fibers

Each fiber is a complete call stack which can be suspended and resumed, like reified continuations (first-class objects you can pass around) wherefiber/new captures a continuation you can suspend or resume:

(def fiber-func (yield 1) (yield 2)) # must accept arity 0
(def f (fiber/new fiber-func))
(fiber/status f) #=> :new
(resume f) # => 1
(fiber/status f) #=> :pending
(resume f) # => 2
(resume f) # => nil
(fiber/status f) #=> :dead

Fibers handle errors. On errors, control returns to the parent fiber. Pass fiber/new the :e flag like (def f (fiber/new block :e)) to trap an error (other signals propagate normally).

(defn err-func [] (error "oops"))
(def f (fiber/new err-func :e))
(resume f) #=> "oops"
(fiber/status f) #=> :error
(fiber/last-value f) #=> "oops"

Handle these errors with try or protect:

(if (= (fiber/status f) :error)
  (print "caught error: " res)
  (print "value returned: " res))
  
# `try` executes the body, in case of error it binds the error
# and raising fiber in the 2nd clause

(try # execute first block, on error, execute 2nd block 
  (error "oops")
  ([err fib] # bind err and fiber here
   (print "caught error: " err)
   (fiber/status fib)))

(protect # returns [true return-val] or [false err-msg]
  (if (error "oops")
    "All good!"
    "An error!"))

Dynamic variables are scoped to fibers:

(var *my-var* :default)
(defn show-var [] (print (dyn *my-var*)))
(def f (fiber/new (fn [] (setdyn *my-var* :fiber-local) (show-var))))
(resume f) #=> :fiber-local
*my-var* #=> :default

Also read Sogaiu's notes. Pepe offered a simple mental model:

Undocumented Features

Environments

def puts a binding in the environment table which (curenv) shows. But we can use e.g. print without importing anything, how?

Janet looks for keys in a table, then its parent (prototype) etc. There is a "shadow" environment prototype, holding the core language. (all-bindings) will show both. To see the root environment: (pp (table/getproto (curenv)))

(table/setproto (curenv) @{}) will break a Janet process by removing the root-env so you can't use print or anything else!

Ian Henry gooes into its relationship with images.

### in a repl
(def my-module @{:public true})
# (var voice [:unvoiced :voiced]) # do stuff here...
(spit "test.jimage" (make-image (curenv)))

You can then run that image with janet -i test.jimage, but to resume it:

(defn restore-image [image]
 (loop [[k v] :pairs image]
   (put (curenv) k v)))

(restore-image (load-image (slurp "test.jimage")))