summaryrefslogtreecommitdiff
path: root/doc/macros.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/macros.md')
-rw-r--r--doc/macros.md207
1 files changed, 116 insertions, 91 deletions
diff --git a/doc/macros.md b/doc/macros.md
index 1841b14..f1efa48 100644
--- a/doc/macros.md
+++ b/doc/macros.md
@@ -1,4 +1,4 @@
-# Macros.fnl (0.3.0)
+# Macros.fnl (0.4.0)
Macros for Cljlib that implement various facilities from Clojure.
**Table of contents**
@@ -27,7 +27,10 @@ Function signature:
```
Create (anonymous) function of fixed arity.
-Supports multiple arities by defining bodies as lists.
+Accepts optional `name` and `docstring?` as first two arguments,
+followed by single or multiple arity bodies defined as lists. Each
+list starts with `arglist*` vector, which supports destructuring, and
+is followed by `body*` wrapped in implicit `do`.
### Examples
Named function of fixed arity 2:
@@ -136,12 +139,12 @@ namespace tables:
([t1 t2 & tables]
(join (join t1 t2) ((or table.unpack _G.unpack) tables)))) ;; call to `join` resolves to ns.tables.join
-(ns.strings.join "a" "b" "c")
-;; => abc
-(join ["a"] ["b"] ["c"] ["d" "e"])
-;; => ["a" "b" "c" "d" "e"]
-(join "a" "b" "c")
-;; {}
+(assert-eq (ns.strings.join "a" "b" "c") "abc")
+
+(assert-eq (join ["a"] ["b"] ["c"] ["d" "e"])
+ ["a" "b" "c" "d" "e"])
+(assert-eq (join "a" "b" "c")
+ [])
```
Note that this creates a collision and local `join` overrides `join`
@@ -159,10 +162,12 @@ 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.
+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`.
Finally clause is optional, and written as (finally body*). If
present, it must be the last clause in the `try` form, and the only
@@ -184,36 +189,36 @@ Catch all errors, ignore those and return fallback value:
(+ x y)
(catch _ 0)))
-(add nil 1) ;; => 0
+(assert-eq (add nil 1) 0)
```
Catch error and do cleanup:
``` fennel
-(let [tbl []]
- (try
- (table.insert tbl "a")
- (table.insert tbl "b" "c")
- (catch _
- (each [k _ (pairs tbl)]
- (tset tbl k nil))))
- tbl)
-;; => {}
+(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 res (try 10 (finally (print "side-effect!"))))
-;; => side-effect!
-;; => nil
-res
-;; => 10
-(local res (try (error 10) (catch 10 nil) (finally (print "side-effect!"))))
-;; => side-effect!
-;; => nil
-res
-;; => nil
+(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)
```
@@ -224,15 +229,22 @@ Function signature:
(def attr-map? name expr)
```
-Wrapper around `local` which can
-declare variables inside namespace, and as local at the same time
-similarly to [`fn*`](#fn*):
+Wrapper around `local` which can declare variables inside namespace,
+and as local `name` at the same time similarly to
+[`fn*`](#fn*). Accepts optional `attr-map?` which can contain a
+docstring, and whether variable should be mutable or not. Sets
+variable to the result of `expr`.
``` fennel
(def ns {})
(def a 10) ;; binds `a` to `10`
+(assert-eq a 10)
+
(def ns.b 20) ;; binds `ns.b` and `b` to `20`
+
+(assert-eq b 20)
+(assert-eq ns.b 20)
```
`a` is a `local`, and both `ns.b` and `b` refer to the same value.
@@ -263,42 +275,48 @@ Function signature:
```
Works the same as [`def`](#def), but ensures that later `defonce`
-calls will not override existing bindings:
+calls will not override existing bindings. Accepts same `attr-map?` as
+`def`, and sets `name` to the result of `expr`:
``` fennel
(defonce a 10)
(defonce a 20)
-(print a) ;; => prints 10
+(assert-eq a 10)
```
## `defmulti`
Function signature:
```
-(defmulti name docstring? dispatch-fn attr-map?)
+(defmulti name docstring? dispatch-fn options*)
```
-Create multifunction with
-runtime dispatching based on results from `dispatch-fn`. Returns an
-empty 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`.
+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
-[`multimethod`](#multimethod) on how to add one.
+[`defmethod`](#defmethod) on how to add one.
## `defmethod`
Function signature:
```
-(defmethod multifn dispatch-val fnspec)
+(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 function tail
-starting from argument list, followed by function body as in
-[`fn*`](#fn).
+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.
@@ -315,7 +333,7 @@ to another multimethod:
(defmethod fac 0 [_] 1)
(defmethod fac :default [x] (* x (fac (- x 1))))
-(fac 4) ;; => 24
+(assert-eq (fac 4) 24)
```
`:default` is a special method which gets called when no other methods
@@ -348,22 +366,30 @@ tables to Lua's one:
(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 :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))
-(print (to-lua-str {:a {:b 10}}))
-;; => {["a"] = {["b"] = 10}}
+(assert-eq (to-lua-str {:a {:b 10}}) "{[\"a\"] = {[\"b\"] = 10}}")
-(print (to-lua-str [:a :b :c [:d {:e :f}]]))
-;; => {[1] = "a", [2] = "b", [3] = "c", [4] = {[1] = "d", [2] = {["e"] = "f"}}}
+(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 if we want.
+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.
## `into`
Function signature:
@@ -372,7 +398,7 @@ Function signature:
(into to from)
```
-Transform one table into another. Mutates first table.
+Transform table `from` into another table `to`. Mutates first table.
Transformation happens in runtime, but type deduction happens in
compile time if possible. This means, that if literal values passed
@@ -380,15 +406,15 @@ to `into` this will have different effects for associative tables and
vectors:
``` fennel
-(into [1 2 3] [4 5 6]) ;; => [1 2 3 4 5 6]
-(into {:a 1 :c 2} {:a 0 :b 1}) ;; => {:a 0 :b 1 :c 2}
+(assert-eq (into [1 2 3] [4 5 6]) [1 2 3 4 5 6])
+(assert-eq (into {:a 1 :c 2} {:a 0 :b 1}) {:a 0 :b 1 :c 2})
```
Conversion between different table types is also supported:
``` fennel
-(into [] {:a 1 :b 2 :c 3}) ;; => [[:a 1] [:b 2] [:c 3]]
-(into {} [[:a 1] [:b 2]]) ;; => {:a 1 :b 2}
+(assert-eq (into [] {:a 1}) [[:a 1]])
+(assert-eq (into {} [[:a 1] [:b 2]]) {:a 1 :b 2})
```
Same rules apply to runtime detection of table type, except that this
@@ -396,7 +422,7 @@ will not work for empty tables:
``` fennel
(local empty-table {})
-(into empty-table {:a 1 :b 2}) ;; => [[:a 1] [:b 2]]
+(assert-eq (into empty-table {:a 1}) [[:a 1]])
``` fennel
If table is empty, `into` defaults to sequential table, because it
@@ -408,8 +434,8 @@ runtime, and this works as expected:
``` fennel
(local t1 [1 2 3])
(local t2 {:a 10 :c 3})
-(into t1 {:a 1 :b 2}) ;; => [1 2 3 [:a 1] [:b 2]]
-(into t2 {:a 1 :b 2}) ;; => {:a 1 :b 2 :c 3}
+(assert-eq (into t1 {:a 1}) [1 2 3 [:a 1]])
+(assert-eq (into t2 {:a 1}) {:a 1 :c 3})
```
`cljlib.fnl` module provides two additional functions `vector` and
@@ -417,8 +443,8 @@ runtime, and this works as expected:
at runtime:
``` fennel
-(into (vector) {:a 1 :b 2}) ;; => [[:a 1] [:b 2]]
-(into (hash-map) [[:a 1 :b 2]]) ;; => {:a 1 :b 2}
+(assert-eq (into (vector) {:a 1}) [[:a 1]])
+(assert-eq (into (hash-map) [[:a 1] [:b 2]]) {:a 1 :b 2})
```
## `empty`
@@ -442,10 +468,10 @@ and return result of the same type:
(table.insert res (f v)))
(into (empty tbl) res)))
-(map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3})
-;; => {:A 1 :B 2 :C 3}
-(map #(* $ $) [1 2 3 4])
-;; [1 4 9 16]
+(assert-eq (map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3})
+ {:A 1 :B 2 :C 3})
+(assert-eq (map #(* $ $) [1 2 3 4])
+ [1 4 9 16])
```
See [`into`](#into) for more info on how conversion is done.
@@ -456,11 +482,11 @@ Function signature:
(when-meta [& body])
```
-Wrapper that compiles away if metadata support was not enabled. What
-this effectively means, is that everything that is wrapped with this
-macro will disappear from the resulting Lua code if metadata is not
-enabled when compiling with `fennel --compile` without `--metadata`
-switch.
+Wrapper that compiles away if metadata support was not enabled.
+What this effectively means, is that everything that is wrapped with
+this macro and its `body` will disappear from the resulting Lua code
+if metadata is not enabled when compiling with `fennel --compile`
+without `--metadata` switch.
## `with-meta`
Function signature:
@@ -469,7 +495,7 @@ Function signature:
(with-meta value meta)
```
-Attach metadata to a value. When metadata feature is not enabled,
+Attach `meta` to a `value`. When metadata feature is not enabled,
returns the value without additional metadata.
``` fennel
@@ -526,9 +552,9 @@ Function signature:
(if-let [binding test] then-branch else-branch)
```
-If test is logical true,
-evaluates `then-branch` with binding-form bound to the value of test,
-if not, yields `else-branch`.
+If `binding` is set by `test` to logical true, evaluates `then-branch`
+with binding-form bound to the value of test, if not, yields
+`else-branch`.
## `when-let`
Function signature:
@@ -537,8 +563,8 @@ Function signature:
(when-let [binding test] & body)
```
-If test is logical true,
-evaluates `body` in implicit `do`.
+If `binding` was bound by `test` to logical true, evaluates `body` in
+implicit `do`.
## `if-some`
Function signature:
@@ -547,9 +573,8 @@ Function signature:
(if-some [binding test] then-branch else-branch)
```
-If test is non-`nil`, evaluates
-`then-branch` with binding-form bound to the value of test, if not,
-yields `else-branch`.
+If `test` is non-`nil`, evaluates `then-branch` with `binding`-form bound
+to the value of test, if not, yields `else-branch`.
## `when-some`
Function signature:
@@ -558,8 +583,8 @@ Function signature:
(when-some [binding test] & body)
```
-If test is non-`nil`,
-evaluates `body` in implicit `do`.
+If `test` sets `binding` to non-`nil`, evaluates `body` in implicit
+`do`.
---
@@ -569,5 +594,5 @@ Copyright (C) 2020 Andrey Orst
License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE)
-<!-- Generated with Fenneldoc 0.0.7
+<!-- Generated with Fenneldoc 0.1.0
https://gitlab.com/andreyorst/fenneldoc -->