diff options
Diffstat (limited to 'doc/cljlib-macros.md')
| -rw-r--r-- | doc/cljlib-macros.md | 581 |
1 files changed, 0 insertions, 581 deletions
diff --git a/doc/cljlib-macros.md b/doc/cljlib-macros.md deleted file mode 100644 index a29fb0f..0000000 --- a/doc/cljlib-macros.md +++ /dev/null @@ -1,581 +0,0 @@ -# Cljlib-macros.fnl (0.3.0) -Macros for Cljlib that implement various facilities from Clojure. - -**Table of contents** - -- [`fn*`](#fn*) -- [`try`](#try) -- [`def`](#def) -- [`defonce`](#defonce) -- [`defmulti`](#defmulti) -- [`defmethod`](#defmethod) -- [`into`](#into) -- [`empty`](#empty) -- [`when-meta`](#when-meta) -- [`with-meta`](#with-meta) -- [`meta`](#meta) -- [`if-let`](#if-let) -- [`when-let`](#when-let) -- [`if-some`](#if-some) -- [`when-some`](#when-some) - -## `fn*` -Function signature: - -``` -(fn* name docstring? ([arglist*] body)*) -``` - -Create (anonymous) function of fixed arity. -Supports multiple arities by defining bodies as lists: - -### Examples -Named function of fixed arity 2: - -``` fennel -(fn* f [a b] (+ a b)) -``` - -Function of fixed arities 1 and 2: - -``` fennel -(fn* ([x] x) - ([x y] (+ x y))) -``` - -Named function of 2 arities, one of which accepts 0 arguments, and the -other one or more arguments: - -``` fennel -(fn* f - ([] nil) - ([x & xs] - (print x) - (f (unpack xs)))) -``` - -Note, that this function is recursive, and calls itself with less and -less amount of arguments until there's no arguments, and terminates -when the zero-arity body is called. - -Named functions accept additional documentation string before the -argument list: - -``` fennel -(fn* cube - "raise `x` to power of 3" - [x] - (^ x 3)) - -(fn* greet - "greet a `person`, optionally specifying default `greeting`." - ([person] (print (.. "Hello, " person "!"))) - ([greeting person] (print (.. greeting ", " person "!")))) -``` - -Argument lists follow the same destruction rules as per `let`. -Variadic arguments with `...` are not supported use `& rest` instead. -Note that only one arity with `&` is supported. - -##### Namespaces -If function name contains namespace part, defines local variable -without namespace part, then creates function with this name, sets -this function to the namespace, and returns it. - -This roughly means, that instead of writing this: - -``` fennel -(local ns {}) - -(fn f [x] ;; we have to define `f` without `ns` - (if (> x 0) (f (- x 1)))) ;; because we're going to use it in `g` - -(set ns.f f) - -(fn ns.g [x] (f (* x 100))) ;; `g` can be defined as `ns.g` as it is only exported - -ns -``` - -It is possible to write: - -``` fennel -(local ns {}) - -(fn* ns.f [x] - (if (> x 0) (f (- x 1)))) - -(fn* ns.g [x] (f (* x 100))) ;; we can use `f` here no problem - -ns -``` - -It is still possible to call `f` and `g` in current scope without `ns` -part, so functions can be reused inside the module, and `ns` will hold -both functions, so it can be exported from the module. - -Note that `fn` will not create the `ns` for you, hence this is just a -syntax sugar. Functions deeply nested in namespaces require exising -namespace tables: - -``` fennel -(local ns {:strings {} - :tables {}}) - -(fn* ns.strings.join - ([s1 s2] (.. s1 s2)) - ([s1 s2 & strings] - (join (join s1 s2) (unpack strings)))) ;; call `join` resolves to ns.strings.join - -(fn* ns.tables.join - ([t1 t2] - (let [res []] - (each [_ v (ipairs t1)] (table.insert res v)) - (each [_ v (ipairs t2)] (table.insert res v)) - res)) - ([t1 t2 & tables] - (join (join t1 t2) (unpack tables)))) ;; call to `join` resolves to ns.tables.join -``` - -Note that this creates a collision and local `join` overrides `join` -from `ns.strings`, so the latter must be fully qualified -`ns.strings.join` when called outside of the function: - -``` fennel -(ns.strings.join "a" "b" "c") -;; => abc -(join ["a"] ["b"] ["c"] ["d" "e"]) -;; => ["a" "b" "c" "d" "e"] -(join "a" "b" "c") -;; {} -``` - -## `try` -Function signature: - -``` -(try body* catch-clause* finally-clause?) -``` - -General purpose try/catch/finally macro. - -(try expression* catch-clause* finally-clause?) - -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. - -Finally-clause is optional, and written as (finally body*). If -present, it must be the last clause in the `try` form, and the only -`finally` clause. Note that `finally` clause is for side effects -only, and runs either after succesful run of `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` 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))) - -(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) -{} -``` - -Always run some side effect action: - -``` fennel ->> (local res (try 10 (finally (print "side-effect!"))) -side-effect! -nil ->> rese0 ->> (local res (try (error 10) (catch 10 nil) (finally (print "side-effect!"))) -side-effect! -nil ->> res -nil -``` - - -## `def` -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*): - -``` fennel -(def ns {}) -(def a 10) ;; binds `a` to `10` - -(def ns.b 20) ;; binds `ns.b` and `b` to `20` -``` - -`a` is a `local`, and both `ns.b` and `b` refer to the same value. - -Additionally metadata can be attached to values, by providing -attribute map or keyword as first parameter. Only one keyword is -supported, which is `:mutable`, which allows mutating variable with -`set` later on: - -``` fennel -;; Bad, will override existing documentation for 299792458 (if any) -(def {:doc "speed of light in m/s"} c 299792458) -(set c 0) ;; => error, can't mutate `c` - -(def :mutable address "Lua St.") ;; same as (def {:mutable true} address "Lua St.") -(set address "Lisp St.") ;; can mutate `address` -``` - -However, attaching documentation metadata to anything other than -tables and functions considered bad practice, due to how Lua -works. More info can be found in [`with-meta`](#with-meta) -description. - -## `defonce` -Function signature: - -``` -(defonce attr-map? name expr) -``` - -Works the same as [`def`](#def), but ensures that later `defonce` -calls will not override existing bindings: - -``` fennel -(defonce a 10) -(defonce a 20) -(print a) ;; => prints 10 -``` - -## `defmulti` -Function signature: - -``` -(defmulti name docstring? dispatch-fn attr-map?) -``` - -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`. - -By default, multifunction has no multimethods, see -[`multimethod`](#multimethod) on how to add one. - -## `defmethod` -Function signature: - -``` -(defmethod multifn dispatch-val 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). - -### 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 -(defmulti fac (fn [x] x)) - -(defmethod fac 0 [_] 1) -(defmethod fac :default [x] (* x (fac (- x 1)))) - -(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 -(defmulti foo (fn* ([x] [x]) ([x y] [x y]))) - -(defmethod foo [10] [_] (print "I've knew I'll get 10")) -(defmethod foo [10 20] [_ _] (print "I've 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've knew I'll get 10"`, and calling -`(foo 10 20)` will print `"I've 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 -(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)) -``` - -And if we call it on some table, we'll get a valid Lua table: - -``` fennel -(print (to-lua-str {:a {:b 10}})) -;; prints {["a"] = {["b"] = 10}} - -(print (to-lua-str [:a :b :c [:d {:e :f}]])) -;; prints {[1] = "a", [2] = "b", [3] = "c", [4] = {[1] = "d", [2] = {["e"] = "f"}}} -``` - -Which we can then reformat as we want and use in Lua if we want. - -## `into` -Function signature: - -``` -(into to from) -``` - -Transform one table into another. Mutates first table. - -Transformation happens in runtime, but type deduction happens in -compile time if possible. This means, that if literal values passed -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} -``` - -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} -``` - -Same rules apply to runtime detection of table type, except that this -will not work for empty tables: - -``` fennel -(local empty-table {}) -(into empty-table {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] -``` fennel - -If table is empty, `into` defaults to sequential table, because it -allows safe conversion from both sequential and associative tables. - -Type for non empty tables hidden in variables can be deduced at -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} -``` - -`cljlib.fnl` module provides two additional functions `vector` and -`hash-map`, that can create empty tables, which can be distinguished -at runtime: - -``` fennel -(into (vector) {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] -(into (hash-map) [[:a 1 :b 2]]) ;; => {:a 1 :b 2} -``` - -## `empty` -Function signature: - -``` -(empty x) -``` - -Return empty table of the same kind as input table `x`, with -additional metadata indicating its type. - -### Example -Creating a generic `map` function, that will work on any table type, -and return result of the same type: - -``` fennel -(fn map [f tbl] - (let [res []] - (each [_ v (ipairs (into [] tbl))] - (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] -``` -See [`into`](#into) for more info on how conversion is done. - -## `when-meta` -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. - -## `with-meta` -Function signature: - -``` -(with-meta value meta) -``` - -Attach metadata to a value. When metadata feature is not enabled, -returns the value without additional metadata. - -``` fennel ->> (local foo (with-meta (fn [...] (let [[x y z] [...]] (+ x y z))) - {:fnl/arglist ["x" "y" "z" "..."] - :fnl/docstring "sum first three values"})) ->> (doc foo) -(foo x y z ...) - sum first three values -``` - -## `meta` -Function signature: - -``` -(meta value) -``` - -Get `value` metadata. If value has no metadata, or metadata -feature is not enabled returns `nil`. - -### Example - -``` fennel ->> (meta (with-meta {} {:meta "data"})) -;; => {:meta "data"} -``` - -### Note -There are several important gotchas about using metadata. - -First, note that this works only when used with Fennel, and only when -`(require fennel)` works. For compiled Lua library this feature is -turned off. - -Second, try to avoid using metadata with anything else than tables and -functions. When storing function or table as a key into metatable, -its address is used, while when storing string of number, the value is -used. This, for example, may cause documentation collision, when -you've set some variable holding a number value to have certain -docstring, and later you've defined another variable with the same -value, but different docstring. While this isn't a major breakage, it -may confuse if someone will explore your code in the REPL with `doc`. - -Lastly, note that prior to Fennel 0.7.1 `import-macros` wasn't -respecting `--metadata` switch. So if you're using Fennel < 0.7.1 -this stuff will only work if you use `require-macros` instead of -`import-macros`. - -## `if-let` -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`. - -## `when-let` -Function signature: - -``` -(when-let [binding test] & body) -``` - -If test is logical true, -evaluates `body` in implicit `do`. - -## `if-some` -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`. - -## `when-some` -Function signature: - -``` -(when-some [binding test] & body) -``` - -If test is non-`nil`, -evaluates `body` in implicit `do`. - - ---- - -Copyright (C) 2020 Andrey Orst - -License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) - - -<!-- Generated with Fenneldoc 0.0.6 - https://gitlab.com/andreyorst/fenneldoc --> |