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")))