diff options
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/cljlib.md (renamed from doc/core.md) | 615 | ||||
| -rw-r--r-- | doc/macros.md | 559 |
2 files changed, 566 insertions, 608 deletions
diff --git a/doc/core.md b/doc/cljlib.md index 578efef..b1678f5 100644 --- a/doc/core.md +++ b/doc/cljlib.md @@ -1,10 +1,8 @@ -# Core (v1.1.1) -Fennel-cljlib - functions from Clojure's core.clj implemented on top -of Fennel. +# Cljlib (v1.1.1) +Fennel-cljlib - functions from Clojure's core.clj implemented on top of Fennel. -This library contains a set of functions providing functions that -behave similarly to Clojure's equivalents. Library itself has nothing -Fennel specific, so it should work on Lua, e.g: +This library contains a set of functions providing functions that behave similarly to Clojure's equivalents. +The library itself apart from macros has nothing Fennel-specific, so it should work on Lua, e.g.: ``` lua Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio @@ -13,41 +11,45 @@ Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio -- 1 4 9 ``` -This example is mapping an anonymous `function` over a table, -producing new table and concatenating it with `" "`. +This example is mapping an anonymous `function` over a table, producing a new table, and concatenating it with `" "`. -However, this library also provides Fennel-specific set of -[macros](./macros.md), that provides additional facilities like `defn` -or `defmulti` which extend the language allowing writing code that -looks and works mostly like Clojure. +However, this library also provides a Fennel-specific set of [macros](./macros.md), which provides additional facilities like [`defn`](#defn) or [`defmulti`](#defmulti) which extends the language allowing writing code that looks and works mostly like Clojure. -Each function in this library is created with `defn`, which is a -special macro for creating multi-arity functions. So when you see -function signature like `(foo [x])`, this means that this is function -`foo`, that accepts exactly one argument `x`. In contrary, functions -created with `fn` will produce `(foo x)` signature (`x` is not inside -brackets). +Each function in this library is created with [`defn`](#defn), which is a special macro for creating multi-arity functions. +So when you see a function signature like `(foo [x])`, this means that this is function `foo`, which accepts exactly one argument `x`. +On the contrary, functions created with `fn` will produce a `(foo x)` signature (`x` is not inside brackets). -Functions, which signatures look like `(foo ([x]) ([x y]) ([x y & -zs]))`, it is a multi-arity function, which accepts either one, two, -or three-or-more arguments. Each `([...])` represents different body -of a function which is chosen by checking amount of arguments passed -to the function. See [Clojure's doc section on multi-arity -functions](https://clojure.org/guides/learn/functions#_multi_arity_functions). +Functions, which signatures look like `(foo ([x]) ([x y]) ([x y & zs]))`, it is a multi-arity function, which accepts either one, two, or three or more arguments. +Each `([...])` represents a different body of a function which is chosen by checking the amount of arguments passed to the function. +See [Clojure's doc section on multi-arity functions](https://clojure.org/guides/learn/functions#_multi_arity_functions). ## Compatibility -This library is mainly developed with Lua 5.4, and tested against -Lua 5.2, 5.3, 5.4, and LuaJIT 2.1.0-beta3. Note, that in lua 5.2 and -LuaJIT equality semantics are a bit different from Lua 5.3 and Lua 5.4. -Main difference is that when comparing two tables, they must have -exactly the same `__eq` metamethods, so comparing hash sets with hash -sets will work, but comparing sets with other tables works only in -Lua5.3+. Another difference is that Lua 5.2 and LuaJIT don't have -inbuilt UTF-8 library, therefore [`seq`](#seq) function will not work for -non-ASCII strings. + +This library is mainly developed with Lua 5.4 and tested against Lua 5.2, 5.3, 5.4, and LuaJIT 2.1.0-beta3. +Note, that in Lua 5.2 and LuaJIT equality semantics are a bit different from Lua 5.3 and Lua 5.4. +The main difference is that when comparing two tables, they must have exactly the same `__eq` metamethods, so comparing hash sets with hash sets will work, but comparing sets with other tables works only in Lua5.3+. +Another difference is that Lua 5.2 and LuaJIT don't have an inbuilt UTF-8 library, therefore [`seq`](#seq) function will not work for non-ASCII strings. **Table of contents** +- [`ns`](#ns) +- [`in-ns`](#in-ns) +- [`def`](#def) +- [`fn*`](#fn) +- [`defn`](#defn) +- [`defn-`](#defn-) +- [`time`](#time) +- [`if-let`](#if-let) +- [`when-let`](#when-let) +- [`if-some`](#if-some) +- [`when-some`](#when-some) +- [`defmulti`](#defmulti) +- [`defmethod`](#defmethod) +- [`cond`](#cond) +- [`loop`](#loop) +- [`try`](#try) +- [`lazy-seq`](#lazy-seq) +- [`lazy-cat`](#lazy-cat) - [`apply`](#apply) - [`add`](#add) - [`sub`](#sub) @@ -152,7 +154,6 @@ non-ASCII strings. - [`iterate`](#iterate) - [`keep`](#keep) - [`keep-indexed`](#keep-indexed) -- [`lazy-seq`](#lazy-seq) - [`line-seq`](#line-seq) - [`list`](#list) - [`list*`](#list-1) @@ -195,6 +196,532 @@ non-ASCII strings. - [`vec`](#vec) - [`zipmap`](#zipmap) +## `ns` +Function signature: + +``` +(ns name commentary requirements) +``` + +Namespace declaration macro. +Accepts the `name` of the generated namespace, and creates a local +variable with this name holding a table. Optionally accepts +`commentary` describing what namespace is about and a `requirements` +spec, specifying what libraries should be required. + +The `requirements` spec is a list that consists of vectors, specifying +library name and a possible alias or a vector of names to refer to +without a prefix: + +``` fennel +(ns some-namespace + "Description of the some-namespace." + (:require [some.lib] + [some.other.lib :as lib2] + [another.lib :refer [foo bar baz]])) + +(defn inc [x] (+ x 1)) +``` + +Which is equivalent to: + +``` fennel +(local some-namespace {}) +(local lib (require :some.lib)) +(local lib2 (require :some.other.lib)) +(local {:bar bar :baz baz :foo foo} (require :another.lib)) +(comment "Description of the some-namespace.") +``` + +Note that when no `:as` alias is given, the library will be named +after the innermost part of the require path, i.e. `some.lib` is +transformed to `lib`. + +See `in-ns` on how to switch namespaces. + +## `in-ns` +Function signature: + +``` +(in-ns name) +``` + +Sets the compile-time variable `cljlib-namespaces` to the given `name`. +Affects such macros as `def`, `defn`, which will bind names to the +specified namespace. + +### Examples +Creating several namespaces in the file, and defining functions in each: + +``` fennel +(ns a) +(defn f [] "f from a") +(ns b) +(defn f [] "f from b") +(in-ns a) +(defn g [] "g from a") +(in-ns b) +(defn g [] "g from b") + +(assert-eq (a.f) "f from a") +(assert-eq (b.f) "f from b") +(assert-eq (a.g) "g from a") +(assert-eq (b.g) "g from b") +``` + +Note, switching namespaces in the REPL doesn't affect non-namespaced +local bindings. In other words, when defining a local with `def`, a +bot a local binding and a namespaced binding are created, and +switching current namespace won't change the local binding: + +``` fennel +>> (ns foo) +nil +>> (def x 42) +nil +>> (ns bar) +nil +>> (def x 1337) +nil +>> (in-ns foo) +#<namespace: foo> +>> x ; user might have expected to see 42 here +1337 +>> foo.x +42 +>> bar.x +1337 +``` + +Sadly, Fennel itself has no support for namespace switching in REPL, +so this feature can be only partially emulated by the cljlib library. + + +## `def` +Function signature: + +``` +(def ([name initializer]) ([meta name initializer])) +``` + +Name binding macro similar to `local` but acts in terms of current +namespace set with the `ns` macro, unless `:private` was passed before +the binding name. Accepts the `name` to be bound and the `initializer` +expression. `meta` can be either an associative table where keys are +strings, or a string representing a key from the table. If a sole +string is given, its value is set to `true` in the meta table. + +## `fn*` +Function signature: + +``` +(fn* ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) +``` + +Clojure-inspired `fn` macro for defining functions. +Accepts an optional `name` and `docstring?`, followed by the binding +list containing function's `params*`. The `body` is wrapped in an +implicit `do`. The `doc-string?` argument specifies an optional +documentation for the function. Supports multi-arity dispatching via +the following syntax: + +(fn* optional-name + optional-docstring + ([arity1] body1) + ([other arity2] body2)) + +Accepts `pre-post?` conditions in a form of a table after argument +list: + +(fn* optional-name + optional-docstring + [arg1 arg2] + {:pre [(check1 arg1 arg2) (check2 arg1)] + :post [(check1 $) ... (checkN $)]} + body) + +The same syntax applies to multi-arity version. + +(pre- and post-checks are not yet implemented) + +## `defn` +Function signature: + +``` +(defn ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) +``` + +Same as `(def name (fn* name docstring? [params*] pre-post? exprs*))` +or `(def name (fn* name docstring? ([params*] pre-post? exprs*)+))` +with any doc-string or attrs added to the function metadata. Accepts +`name` which will be used to refer to a function in the current +namespace, and optional `doc-string?`, a vector of function's +`params*`, `pre-post?` conditions, and the `body` of the function. +The body is wrapped in an implicit do. See `fn*` for more info. + +## `defn-` +Function signature: + +``` +(defn- ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) +``` + +Same as `(def :private name (fn* name docstring? [params*] pre-post? +exprs*))` or `(def :private name (fn* name docstring? ([params*] +pre-post? exprs*)+))` with any doc-string or attrs added to the +function metadata. Accepts `name` which will be used to refer to a +function, and optional `doc-string?`, a vector of function's +`params*`, `pre-post?` conditions, and the `body` of the function. +The body is wrapped in an implicit do. See `fn*` for more info. + +## `time` +Function signature: + +``` +(time expr) +``` + +Measure the CPU time spent executing `expr`. + +## `if-let` +Function signature: + +``` +(if-let [name test] if-branch else-branch) +``` + +When `test` is logical `true`, evaluates the `if-branch` with `name` +bound to the value of `test`. Otherwise, evaluates the `else-branch` + +## `when-let` +Function signature: + +``` +(when-let [name test] & body) +``` + +When `test` is logical `true`, evaluates the `body` with `name` bound +to the value of `test`. + +## `if-some` +Function signature: + +``` +(if-some [name test] if-branch else-branch) +``` + +When `test` is not `nil`, evaluates the `if-branch` with `name` +bound to the value of `test`. Otherwise, evaluates the `else-branch` + +## `when-some` +Function signature: + +``` +(when-some [name test] & body) +``` + +When `test` is not `nil`, evaluates the `body` with `name` bound to +the value of `test`. + +## `defmulti` +Function signature: + +``` +(defmulti name docstring? dispatch-fn options*) +``` + +Create multifunction `name` with runtime dispatching based on results +from `dispatch-fn`. Returns a proxy table with `__call` metamethod, +that calls `dispatch-fn` on its arguments. Amount of arguments +passed, should be the same as accepted by `dispatch-fn`. Looks for +multimethod based on result from `dispatch-fn`. + +Accepts optional `docstring?`, and `options*` arguments, where +`options*` is a sequence of key value pairs representing additional +attributes. Supported options: + +`:default` - the default dispatch value, defaults to `:default`. + +By default, multifunction has no multimethods, see +[`defmethod`](#defmethod) on how to add one. + +## `defmethod` +Function signature: + +``` +(defmethod multi-fn dispatch-value fnspec) +``` + +Attach new method to multi-function dispatch value. Accepts the +`multi-fn` as its first argument, the `dispatch-value` as second, and +`fnspec` - a function tail starting from argument list, followed by +function body as in [`fn*`](#fn). + +### Examples +Here are some examples how multimethods can be used. + +#### Factorial example +Key idea here is that multimethods can call itself with different +values, and will dispatch correctly. Here, `fac` recursively calls +itself with less and less number until it reaches `0` and dispatches +to another multimethod: + +``` fennel +(ns test) + +(defmulti fac (fn [x] x)) + +(defmethod fac 0 [_] 1) +(defmethod fac :default [x] (* x (fac (- x 1)))) + +(assert-eq (fac 4) 24) +``` + +`:default` is a special method which gets called when no other methods +were found for given dispatch value. + +#### Multi-arity dispatching +Multi-arity function tails are also supported: + +``` fennel +(ns test) + +(defmulti foo (fn* ([x] [x]) ([x y] [x y]))) + +(defmethod foo [10] [_] (print "I knew I'll get 10")) +(defmethod foo [10 20] [_ _] (print "I knew I'll get both 10 and 20")) +(defmethod foo :default ([x] (print (.. "Umm, got" x))) + ([x y] (print (.. "Umm, got both " x " and " y)))) +``` + +Calling `(foo 10)` will print `"I knew I'll get 10"`, and calling +`(foo 10 20)` will print `"I knew I'll get both 10 and 20"`. +However, calling `foo` with any other numbers will default either to +`"Umm, got x"` message, when called with single value, and `"Umm, got +both x and y"` when calling with two values. + +#### Dispatching on object's type +We can dispatch based on types the same way we dispatch on values. +For example, here's a naive conversion from Fennel's notation for +tables to Lua's one: + +``` fennel +(ns test) + +(defmulti to-lua-str (fn [x] (type x))) + +(defmethod to-lua-str :number [x] (tostring x)) +(defmethod to-lua-str :table [x] + (let [res []] + (each [k v (pairs x)] + (table.insert res (.. "[" (to-lua-str k) "] = " (to-lua-str v)))) + (.. "{" (table.concat res ", ") "}"))) +(defmethod to-lua-str :string [x] (.. "\"" x "\"")) +(defmethod to-lua-str :default [x] (tostring x)) + +(assert-eq (to-lua-str {:a {:b 10}}) "{[\"a\"] = {[\"b\"] = 10}}") + +(assert-eq (to-lua-str [:a :b :c [:d {:e :f}]]) + "{[1] = \"a\", [2] = \"b\", [3] = \"c\", [4] = {[1] = \"d\", [2] = {[\"e\"] = \"f\"}}}") +``` + +And if we call it on some table, we'll get a valid Lua table, which we +can then reformat as we want and use in Lua. + +All of this can be done with functions, and single entry point +function, that uses if statement and branches on the type, however one +of the additional features of multimethods, is that separate libraries +can extend such multimethod by adding additional claues to it without +needing to patch the source of the function. For example later on +support for userdata or coroutines can be added to `to-lua-str` +function as a separate multimethods for respective types. + +## `cond` +Function signature: + +``` +(cond ...) +``` + +Takes a set of test expression pairs. It evaluates each test one at a +time. If a test returns logical true, `cond` evaluates and returns +the value of the corresponding expression and doesn't evaluate any of +the other tests or exprs. `(cond)` returns nil. + +## `loop` +Function signature: + +``` +(loop binding-vec body*) +``` + +Recursive loop macro. + +Similar to `let`, but binds a special `recur` call that will reassign +the values of the `binding-vec` and restart the loop `body*`. Unlike +`let`, doesn't support multiple-value destructuring. + +The first argument is a binding table with alternating symbols (or destructure +forms), and the values to bind to them. + +For example: + +``` fennel +(loop [[first & rest] [1 2 3 4 5] + i 0] + (if (= nil first) + i + (recur rest (+ 1 i)))) +``` + +This would destructure the first table argument, with the first value inside it +being assigned to `first` and the remainder of the table being assigned to +`rest`. `i` simply gets bound to 0. + +The body of the form executes for every item in the table, calling `recur` each +time with the table lacking its head element (thus consuming one element per +iteration), and with `i` being called with one value greater than the previous. + +When the loop terminates (When the user doesn't call `recur`) it will return the +number of elements in the passed in table. (In this case, 5) + +### Limitations + +In order to only evaluate expressions once and support sequential +bindings, the binding table has to be transformed like this: + +``` fennel +(loop [[x & xs] (foo) + y (+ x 1)] + ...) + +(let [_1_ (foo) + [x & xs] _1_ + _2_ (+ x 1) + y _2_] + ((fn recur [[x & xs] y] ...) _1_ _2_) +``` + +This ensures that `foo` is called only once, its result is cached in a +`sym1#` binding, and that `y` can use the destructured value, obtained +from that binding. The value of this binding is later passed to the +function to begin the first iteration. + +This has two unfortunate consequences. One is that the initial +destructuring happens twice - first, to make sure that later bindings +can be properly initialized, and second, when the first looping +function call happens. Another one is that as a result, `loop` macro +can't work with multiple-value destructuring, because these can't be +cached as described above. E.g. this will not work: + +``` fennel +(loop [(x y) (foo)] ...) +``` + +Because it would be transformed to: + +``` fennel +(let [_1_ (foo) + (x y) _1_] + ((fn recur [(x y)] ...) _1_) +``` + +`x` is correctly set, but `y` is completely lost. Therefore, this +macro checks for lists in bindings. + +## `try` +Function signature: + +``` +(try body* catch-clause* finally-clause?) +``` + +General purpose try/catch/finally macro. +Wraps its body in `pcall` and checks the return value with `match` +macro. + +Catch clause is written either as `(catch symbol body*)`, thus acting +as catch-all, or `(catch value body*)` for catching specific errors. +It is possible to have several `catch` clauses. If no `catch` clauses +specified, an implicit catch-all clause is created. `body*`, and +inner expressions of `catch-clause*`, and `finally-clause?` are +wrapped in implicit `do`. + +The `finally` clause is optional, and written as (finally body*). If +present, it must be the last clause in the [`try`](#try) form, and the only +`finally` clause. Note that `finally` clause is for side effects +only, and runs either after succesful run of [`try`](#try) body, or after any +`catch` clause body, before returning the result. If no `catch` +clause is provided `finally` runs in implicit catch-all clause, and +trows error to upper scope using `error` function. + +To throw error from [`try`](#try) to catch it with `catch` clause use `error` +or `assert` functions. + +### Examples +Catch all errors, ignore those and return fallback value: + +``` fennel +(fn add [x y] + (try + (+ x y) + (catch _ 0))) + +(assert-eq (add nil 1) 0) +``` + +Catch error and do cleanup: + +``` fennel +(local tbl []) + +(try + (table.insert tbl "a") + (table.insert tbl "b" "c") + (catch _ + (each [k _ (pairs tbl)] + (tset tbl k nil)))) + +(assert-eq (length tbl) 0) + +``` + +Always run some side effect action: + +``` fennel +(local t []) +(local res (try 10 (finally (table.insert t :finally)))) +(assert-eq (. t 1) :finally) +(assert-eq res 10) + +(local res (try (error 10) (catch 10 nil) (finally (table.insert t :again)))) +(assert-eq (. t 2) :again) +(assert-eq res nil) +``` + +## `lazy-seq` +Function signature: + +``` +(lazy-seq & body) +``` + +Takes a `body` of expressions that returns a sequence, table or nil, +and yields a lazy sequence that will invoke the body only the first +time `seq` is called, and will cache the result and return it on all +subsequent `seq` calls. See also - `realized?` + +## `lazy-cat` +Function signature: + +``` +(lazy-cat & colls) +``` + +Expands to code which yields a lazy sequence of the concatenation of +`colls` - expressions returning collections. Each expression is not +evaluated until it is needed. + ## `apply` Function signature: @@ -406,7 +933,7 @@ Function signature: Test if `mf` is an instance of `multifn`. -`multifn` is a special kind of table, created with `defmulti` macros +`multifn` is a special kind of table, created with [`defmulti`](#defmulti) macros from `macros.fnl`. ## `set?` @@ -930,6 +1457,7 @@ Returns the nth rest of `coll`, `coll` when `n` is 0. (assert-eq (nthrest [1 2 3 4] 0) [1 2 3 4]) ``` + ## `partition` Function signature: @@ -1524,17 +2052,6 @@ the `coll`. Note, this means false return values will be included. `f` must be free of side effects. Returns a transducer when no collection is provided. -## `lazy-seq` -Function signature: - -``` -(lazy-seq [f]) -``` - -Create lazy sequence from the result of calling a function `f`. -Delays execution of `f` until sequence is consumed. `f` must return a -sequence or a vector. - ## `line-seq` Function signature: @@ -1556,7 +2073,7 @@ Bear in mind, that since the sequence is lazy it should be realized or truncated before the file is closed: ``` fennel -(let [lines (with-open [f (io.open "init.fnl" :r)] +(let [lines (with-open [f (io.open "cljlib.fnl" :r)] (line-seq f))] ;; this will error because only first line was realized, but the ;; file was closed before the rest of lines were cached @@ -1566,7 +2083,7 @@ truncated before the file is closed: Sequence is realized with `doall` before file was closed and can be shared: ``` fennel -(let [lines (with-open [f (io.open "init.fnl" :r)] +(let [lines (with-open [f (io.open "cljlib.fnl" :r)] (doall (line-seq f)))] (assert-is (pcall next lines))) ``` @@ -2095,5 +2612,5 @@ Copyright (C) 2020-2021 Andrey Listopadov License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) -<!-- Generated with Fenneldoc v0.1.9 +<!-- Generated with Fenneldoc v1.0.1 https://gitlab.com/andreyorst/fenneldoc --> diff --git a/doc/macros.md b/doc/macros.md deleted file mode 100644 index 9c20e56..0000000 --- a/doc/macros.md +++ /dev/null @@ -1,559 +0,0 @@ -# Macros (v1.1.1) -Macros for fennel-cljlib. - -**Table of contents** - -- [`cond`](#cond) -- [`def`](#def) -- [`defmethod`](#defmethod) -- [`defmulti`](#defmulti) -- [`defn`](#defn) -- [`defn-`](#defn-) -- [`fn*`](#fn) -- [`if-let`](#if-let) -- [`if-some`](#if-some) -- [`in-ns`](#in-ns) -- [`lazy-cat`](#lazy-cat) -- [`lazy-seq`](#lazy-seq) -- [`loop`](#loop) -- [`ns`](#ns) -- [`time`](#time) -- [`try`](#try) -- [`when-let`](#when-let) -- [`when-some`](#when-some) - -## `cond` -Function signature: - -``` -(cond ...) -``` - -Takes a set of test expression pairs. It evaluates each test one at a -time. If a test returns logical true, `cond` evaluates and returns -the value of the corresponding expression and doesn't evaluate any of -the other tests or exprs. `(cond)` returns nil. - -## `def` -Function signature: - -``` -(def ([name initializer]) ([meta name initializer])) -``` - -Name binding macro similar to `local` but acts in terms of current -namespace set with the `ns` macro, unless `:private` was passed before -the binding name. Accepts the `name` to be bound and the `initializer` -expression. `meta` can be either an associative table where keys are -strings, or a string representing a key from the table. If a sole -string is given, its value is set to `true` in the meta table. - -## `defmethod` -Function signature: - -``` -(defmethod multi-fn dispatch-value fnspec) -``` - -Attach new method to multi-function dispatch value. Accepts the -`multi-fn` as its first argument, the `dispatch-value` as second, and -`fnspec` - a function tail starting from argument list, followed by -function body as in [`fn*`](#fn). - -### Examples -Here are some examples how multimethods can be used. - -#### Factorial example -Key idea here is that multimethods can call itself with different -values, and will dispatch correctly. Here, `fac` recursively calls -itself with less and less number until it reaches `0` and dispatches -to another multimethod: - -``` fennel -(ns test) - -(defmulti fac (fn [x] x)) - -(defmethod fac 0 [_] 1) -(defmethod fac :default [x] (* x (fac (- x 1)))) - -(assert-eq (fac 4) 24) -``` - -`:default` is a special method which gets called when no other methods -were found for given dispatch value. - -#### Multi-arity dispatching -Multi-arity function tails are also supported: - -``` fennel -(ns test) - -(defmulti foo (fn* ([x] [x]) ([x y] [x y]))) - -(defmethod foo [10] [_] (print "I knew I'll get 10")) -(defmethod foo [10 20] [_ _] (print "I knew I'll get both 10 and 20")) -(defmethod foo :default ([x] (print (.. "Umm, got" x))) - ([x y] (print (.. "Umm, got both " x " and " y)))) -``` - -Calling `(foo 10)` will print `"I knew I'll get 10"`, and calling -`(foo 10 20)` will print `"I knew I'll get both 10 and 20"`. -However, calling `foo` with any other numbers will default either to -`"Umm, got x"` message, when called with single value, and `"Umm, got -both x and y"` when calling with two values. - -#### Dispatching on object's type -We can dispatch based on types the same way we dispatch on values. -For example, here's a naive conversion from Fennel's notation for -tables to Lua's one: - -``` fennel -(ns test) - -(defmulti to-lua-str (fn [x] (type x))) - -(defmethod to-lua-str :number [x] (tostring x)) -(defmethod to-lua-str :table [x] - (let [res []] - (each [k v (pairs x)] - (table.insert res (.. "[" (to-lua-str k) "] = " (to-lua-str v)))) - (.. "{" (table.concat res ", ") "}"))) -(defmethod to-lua-str :string [x] (.. "\"" x "\"")) -(defmethod to-lua-str :default [x] (tostring x)) - -(assert-eq (to-lua-str {:a {:b 10}}) "{[\"a\"] = {[\"b\"] = 10}}") - -(assert-eq (to-lua-str [:a :b :c [:d {:e :f}]]) - "{[1] = \"a\", [2] = \"b\", [3] = \"c\", [4] = {[1] = \"d\", [2] = {[\"e\"] = \"f\"}}}") -``` - -And if we call it on some table, we'll get a valid Lua table, which we -can then reformat as we want and use in Lua. - -All of this can be done with functions, and single entry point -function, that uses if statement and branches on the type, however one -of the additional features of multimethods, is that separate libraries -can extend such multimethod by adding additional claues to it without -needing to patch the source of the function. For example later on -support for userdata or coroutines can be added to `to-lua-str` -function as a separate multimethods for respective types. - -## `defmulti` -Function signature: - -``` -(defmulti name docstring? dispatch-fn options*) -``` - -Create multifunction `name` with runtime dispatching based on results -from `dispatch-fn`. Returns a proxy table with `__call` metamethod, -that calls `dispatch-fn` on its arguments. Amount of arguments -passed, should be the same as accepted by `dispatch-fn`. Looks for -multimethod based on result from `dispatch-fn`. - -Accepts optional `docstring?`, and `options*` arguments, where -`options*` is a sequence of key value pairs representing additional -attributes. Supported options: - -`:default` - the default dispatch value, defaults to `:default`. - -By default, multifunction has no multimethods, see -[`defmethod`](#defmethod) on how to add one. - -## `defn` -Function signature: - -``` -(defn ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) -``` - -Same as `(def name (fn* name docstring? [params*] pre-post? exprs*))` -or `(def name (fn* name docstring? ([params*] pre-post? exprs*)+))` -with any doc-string or attrs added to the function metadata. Accepts -`name` which will be used to refer to a function in the current -namespace, and optional `doc-string?`, a vector of function's -`params*`, `pre-post?` conditions, and the `body` of the function. -The body is wrapped in an implicit do. See `fn*` for more info. - -## `defn-` -Function signature: - -``` -(defn- ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) -``` - -Same as `(def :private name (fn* name docstring? [params*] pre-post? -exprs*))` or `(def :private name (fn* name docstring? ([params*] -pre-post? exprs*)+))` with any doc-string or attrs added to the -function metadata. Accepts `name` which will be used to refer to a -function, and optional `doc-string?`, a vector of function's -`params*`, `pre-post?` conditions, and the `body` of the function. -The body is wrapped in an implicit do. See `fn*` for more info. - -## `fn*` -Function signature: - -``` -(fn* ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) -``` - -Clojure-inspired `fn` macro for defining functions. -Accepts an optional `name` and `docstring?`, followed by the binding -list containing function's `params*`. The `body` is wrapped in an -implicit `do`. The `doc-string?` argument specifies an optional -documentation for the function. Supports multi-arity dispatching via -the following syntax: - -(fn* optional-name - optional-docstring - ([arity1] body1) - ([other arity2] body2)) - -Accepts `pre-post?` conditions in a form of a table after argument -list: - -(fn* optional-name - optional-docstring - [arg1 arg2] - {:pre [(check1 arg1 arg2) (check2 arg1)] - :post [(check1 $) ... (checkN $)]} - body) - -The same syntax applies to multi-arity version. - -(pre- and post-checks are not yet implemented) - -## `if-let` -Function signature: - -``` -(if-let [name test] if-branch else-branch) -``` - -When `test` is logical `true`, evaluates the `if-branch` with `name` -bound to the value of `test`. Otherwise, evaluates the `else-branch` - -## `if-some` -Function signature: - -``` -(if-some [name test] if-branch else-branch) -``` - -When `test` is not `nil`, evaluates the `if-branch` with `name` -bound to the value of `test`. Otherwise, evaluates the `else-branch` - -## `in-ns` -Function signature: - -``` -(in-ns name) -``` - -Sets the compile-time variable `cljlib-namespaces` to the given `name`. -Affects such macros as `def`, `defn`, which will bind names to the -specified namespace. - -### Examples -Creating several namespaces in the file, and defining functions in each: - -``` fennel -(ns a) -(defn f [] "f from a") -(ns b) -(defn f [] "f from b") -(in-ns a) -(defn g [] "g from a") -(in-ns b) -(defn g [] "g from b") - -(assert-eq (a.f) "f from a") -(assert-eq (b.f) "f from b") -(assert-eq (a.g) "g from a") -(assert-eq (b.g) "g from b") -``` - -Note, switching namespaces in the REPL doesn't affect non-namespaced -local bindings. In other words, when defining a local with `def`, a -bot a local binding and a namespaced binding are created, and -switching current namespace won't change the local binding: - -``` fennel ->> (ns foo) -nil ->> (def x 42) -nil ->> (ns bar) -nil ->> (def x 1337) -nil ->> (in-ns foo) -#<namespace: foo> ->> x ; user might have expected to see 42 here -1337 ->> foo.x -42 ->> bar.x -1337 -``` - -Sadly, Fennel itself has no support for namespace switching in REPL, -so this feature can be only partially emulated by the cljlib library. - -## `lazy-cat` -Function signature: - -``` -(lazy-cat & colls) -``` - -Expands to code which yields a lazy sequence of the concatenation of -`colls` - expressions returning collections. Each expression is not -evaluated until it is needed. - -## `lazy-seq` -Function signature: - -``` -(lazy-seq & body) -``` - -Takes a `body` of expressions that returns a sequence, table or nil, -and yields a lazy sequence that will invoke the body only the first -time `seq` is called, and will cache the result and return it on all -subsequent `seq` calls. See also - `realized?` - -## `loop` -Function signature: - -``` -(loop binding-vec body*) -``` - -Recursive loop macro. - -Similar to `let`, but binds a special `recur` call that will reassign -the values of the `binding-vec` and restart the loop `body*`. Unlike -`let`, doesn't support multiple-value destructuring. - -The first argument is a binding table with alternating symbols (or destructure -forms), and the values to bind to them. - -For example: - -``` fennel -(loop [[first & rest] [1 2 3 4 5] - i 0] - (if (= nil first) - i - (recur rest (+ 1 i)))) -``` - -This would destructure the first table argument, with the first value inside it -being assigned to `first` and the remainder of the table being assigned to -`rest`. `i` simply gets bound to 0. - -The body of the form executes for every item in the table, calling `recur` each -time with the table lacking its head element (thus consuming one element per -iteration), and with `i` being called with one value greater than the previous. - -When the loop terminates (When the user doesn't call `recur`) it will return the -number of elements in the passed in table. (In this case, 5) - -### Limitations - -In order to only evaluate expressions once and support sequential -bindings, the binding table has to be transformed like this: - -``` fennel -(loop [[x & xs] (foo) - y (+ x 1)] - ...) - -(let [_1_ (foo) - [x & xs] _1_ - _2_ (+ x 1) - y _2_] - ((fn recur [[x & xs] y] ...) _1_ _2_) -``` - -This ensures that `foo` is called only once, its result is cached in a -`sym1#` binding, and that `y` can use the destructured value, obtained -from that binding. The value of this binding is later passed to the -function to begin the first iteration. - -This has two unfortunate consequences. One is that the initial -destructuring happens twice - first, to make sure that later bindings -can be properly initialized, and second, when the first looping -function call happens. Another one is that as a result, `loop` macro -can't work with multiple-value destructuring, because these can't be -cached as described above. E.g. this will not work: - -``` fennel -(loop [(x y) (foo)] ...) -``` - -Because it would be transformed to: - -``` fennel -(let [_1_ (foo) - (x y) _1_] - ((fn recur [(x y)] ...) _1_) -``` - -`x` is correctly set, but `y` is completely lost. Therefore, this -macro checks for lists in bindings. - -## `ns` -Function signature: - -``` -(ns name commentary requirements) -``` - -Namespace declaration macro. -Accepts the `name` of the generated namespace, and creates a local -variable with this name holding a table. Optionally accepts -`commentary` describing what namespace is about and a `requirements` -spec, specifying what libraries should be required. - -The `requirements` spec is a list that consists of vectors, specifying -library name and a possible alias or a vector of names to refer to -without a prefix: - -``` fennel -(ns some-namespace - "Description of the some-namespace." - (:require [some.lib] - [some.other.lib :as lib2] - [another.lib :refer [foo bar baz]])) - -(defn inc [x] (+ x 1)) -``` - -Which is equivalent to: - -``` fennel -(local some-namespace {}) -(local lib (require :some.lib)) -(local lib2 (require :some.other.lib)) -(local {:bar bar :baz baz :foo foo} (require :another.lib)) -(comment "Description of the some-namespace.") -``` - -Note that when no `:as` alias is given, the library will be named -after the innermost part of the require path, i.e. `some.lib` is -transformed to `lib`. - -See `in-ns` on how to switch namespaces. - -## `time` -Function signature: - -``` -(time expr) -``` - -Measure the CPU time spent executing `expr`. - -## `try` -Function signature: - -``` -(try body* catch-clause* finally-clause?) -``` - -General purpose try/catch/finally macro. -Wraps its body in `pcall` and checks the return value with `match` -macro. - -Catch clause is written either as `(catch symbol body*)`, thus acting -as catch-all, or `(catch value body*)` for catching specific errors. -It is possible to have several `catch` clauses. If no `catch` clauses -specified, an implicit catch-all clause is created. `body*`, and -inner expressions of `catch-clause*`, and `finally-clause?` are -wrapped in implicit `do`. - -The `finally` clause is optional, and written as (finally body*). If -present, it must be the last clause in the [`try`](#try) form, and the only -`finally` clause. Note that `finally` clause is for side effects -only, and runs either after succesful run of [`try`](#try) body, or after any -`catch` clause body, before returning the result. If no `catch` -clause is provided `finally` runs in implicit catch-all clause, and -trows error to upper scope using `error` function. - -To throw error from [`try`](#try) to catch it with `catch` clause use `error` -or `assert` functions. - -### Examples -Catch all errors, ignore those and return fallback value: - -``` fennel -(fn add [x y] - (try - (+ x y) - (catch _ 0))) - -(assert-eq (add nil 1) 0) -``` - -Catch error and do cleanup: - -``` fennel -(local tbl []) - -(try - (table.insert tbl "a") - (table.insert tbl "b" "c") - (catch _ - (each [k _ (pairs tbl)] - (tset tbl k nil)))) - -(assert-eq (length tbl) 0) - -``` - -Always run some side effect action: - -``` fennel -(local t []) -(local res (try 10 (finally (table.insert t :finally)))) -(assert-eq (. t 1) :finally) -(assert-eq res 10) - -(local res (try (error 10) (catch 10 nil) (finally (table.insert t :again)))) -(assert-eq (. t 2) :again) -(assert-eq res nil) -``` - -## `when-let` -Function signature: - -``` -(when-let [name test] & body) -``` - -When `test` is logical `true`, evaluates the `body` with `name` bound -to the value of `test`. - -## `when-some` -Function signature: - -``` -(when-some [name test] & body) -``` - -When `test` is not `nil`, evaluates the `body` with `name` bound to -the value of `test`. - - ---- - -Copyright (C) 2020-2021 Andrey Listopadov - -License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) - - -<!-- Generated with Fenneldoc v0.1.9 - https://gitlab.com/andreyorst/fenneldoc --> |