From 270beed0505ef47159d94fb162ff4840958f3ce5 Mon Sep 17 00:00:00 2001 From: Andrey Orst Date: Tue, 19 Jan 2021 16:46:00 +0000 Subject: fix: Fennel 0.8.0 enhancements Changelog: - fixed bug in try - reworked pretty printing for sets - handle cycles in sets - use new fennel.view format - reorganized library to make requiring it easier --- doc/cljlib-macros.md | 581 -------------------------------------------------- doc/cljlib.md | 251 +++++++++------------- doc/macros.md | 588 +++++++++++++++++++++++++++++++++++++++++++++++++++ doc/tests/test.md | 2 +- 4 files changed, 687 insertions(+), 735 deletions(-) delete mode 100644 doc/cljlib-macros.md create mode 100644 doc/macros.md (limited to 'doc') 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) - - - diff --git a/doc/cljlib.md b/doc/cljlib.md index ee686cf..54d8294 100644 --- a/doc/cljlib.md +++ b/doc/cljlib.md @@ -1,10 +1,10 @@ -# Cljlib.fnl (0.3.0) +# Cljlib (0.4.0) 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, e.g: +Fennel specific so it should work on Lua, e.g: ``` lua Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio @@ -35,6 +35,17 @@ 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). +## 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` function will not work for +non-ASCII strings. + **Table of contents** - [`apply`](#apply) @@ -142,12 +153,7 @@ Applying `print` to different arguments: Function signature: ``` -(add - ([a]) - ([a b]) - ([a b c]) - ([a b c d]) - ([a b c d & rest])) +(add ([]) ([a]) ([a b]) ([a b c]) ([a b c d]) ([a b c d & rest])) ``` Sum arbitrary amount of numbers. @@ -156,12 +162,7 @@ Sum arbitrary amount of numbers. Function signature: ``` -(sub - ([a]) - ([a b]) - ([a b c]) - ([a b c d]) - ([a b c d & rest])) +(sub ([]) ([a]) ([a b]) ([a b c]) ([a b c d]) ([a b c d & rest])) ``` Subtract arbitrary amount of numbers. @@ -170,12 +171,7 @@ Subtract arbitrary amount of numbers. Function signature: ``` -(mul - ([a]) - ([a b]) - ([a b c]) - ([a b c d]) - ([a b c d & rest])) +(mul ([]) ([a]) ([a b]) ([a b c]) ([a b c d]) ([a b c d & rest])) ``` Multiply arbitrary amount of numbers. @@ -184,12 +180,7 @@ Multiply arbitrary amount of numbers. Function signature: ``` -(div - ([a]) - ([a b]) - ([a b c]) - ([a b c d]) - ([a b c d & rest])) +(div ([a]) ([a b]) ([a b c]) ([a b c d]) ([a b c d & rest])) ``` Divide arbitrary amount of numbers. @@ -198,10 +189,7 @@ Divide arbitrary amount of numbers. Function signature: ``` -(le - ([x]) - ([x y]) - ([x y & more])) +(le ([x]) ([x y]) ([x y & more])) ``` Returns true if nums are in monotonically non-decreasing order @@ -210,10 +198,7 @@ Returns true if nums are in monotonically non-decreasing order Function signature: ``` -(lt - ([x]) - ([x y]) - ([x y & more])) +(lt ([x]) ([x y]) ([x y & more])) ``` Returns true if nums are in monotonically decreasing order @@ -222,10 +207,7 @@ Returns true if nums are in monotonically decreasing order Function signature: ``` -(ge - ([x]) - ([x y]) - ([x y & more])) +(ge ([x]) ([x y]) ([x y & more])) ``` Returns true if nums are in monotonically non-increasing order @@ -234,10 +216,7 @@ Returns true if nums are in monotonically non-increasing order Function signature: ``` -(gt - ([x]) - ([x y]) - ([x y & more])) +(gt ([x]) ([x y]) ([x y & more])) ``` Returns true if nums are in monotonically increasing order @@ -246,7 +225,7 @@ Returns true if nums are in monotonically increasing order Function signature: ``` -(inc [x]) +(inc ([x])) ``` Increase number by one @@ -255,7 +234,7 @@ Increase number by one Function signature: ``` -(dec [x]) +(dec ([x])) ``` Decrease number by one @@ -264,10 +243,7 @@ Decrease number by one Function signature: ``` -(eq - ([x]) - ([x y]) - ([x y & xs])) +(eq ([x]) ([x y]) ([x y & xs])) ``` Deep compare values. @@ -276,7 +252,7 @@ Deep compare values. Function signature: ``` -(map? [tbl]) +(map? ([tbl])) ``` Check whether `tbl` is an associative table. @@ -318,7 +294,7 @@ Empty tables created with [`hash-map`](#hash-map) will pass the test: Function signature: ``` -(vector? [tbl]) +(vector? ([tbl])) ``` Check whether `tbl` is an sequential table. @@ -360,7 +336,7 @@ Empty tables created with [`vector`](#vector) will pass the test: Function signature: ``` -(multifn? [mf]) +(multifn? ([mf])) ``` Test if `mf` is an instance of `multifn`. @@ -372,7 +348,7 @@ from `cljlib-macros.fnl`. Function signature: ``` -(set? [s]) +(set? ([s])) ``` @@ -381,8 +357,7 @@ Function signature: Function signature: ``` -(nil? - ([x])) +(nil? ([]) ([x])) ``` Test if value is nil. @@ -391,7 +366,7 @@ Test if value is nil. Function signature: ``` -(zero? [x]) +(zero? ([x])) ``` Test if value is equal to zero. @@ -400,7 +375,7 @@ Test if value is equal to zero. Function signature: ``` -(pos? [x]) +(pos? ([x])) ``` Test if `x` is greater than zero. @@ -409,7 +384,7 @@ Test if `x` is greater than zero. Function signature: ``` -(neg? [x]) +(neg? ([x])) ``` Test if `x` is less than zero. @@ -418,7 +393,7 @@ Test if `x` is less than zero. Function signature: ``` -(even? [x]) +(even? ([x])) ``` Test if value is even. @@ -427,7 +402,7 @@ Test if value is even. Function signature: ``` -(odd? [x]) +(odd? ([x])) ``` Test if value is odd. @@ -436,7 +411,7 @@ Test if value is odd. Function signature: ``` -(string? [x]) +(string? ([x])) ``` Test if `x` is a string. @@ -445,7 +420,7 @@ Test if `x` is a string. Function signature: ``` -(boolean? [x]) +(boolean? ([x])) ``` Test if `x` is a Boolean @@ -454,7 +429,7 @@ Test if `x` is a Boolean Function signature: ``` -(true? [x]) +(true? ([x])) ``` Test if `x` is `true` @@ -463,7 +438,7 @@ Test if `x` is `true` Function signature: ``` -(false? [x]) +(false? ([x])) ``` Test if `x` is `false` @@ -472,7 +447,7 @@ Test if `x` is `false` Function signature: ``` -(int? [x]) +(int? ([x])) ``` Test if `x` is a number without floating point data. @@ -483,7 +458,7 @@ Number is rounded with `math.floor` and compared with original number. Function signature: ``` -(pos-int? [x]) +(pos-int? ([x])) ``` Test if `x` is a positive integer. @@ -492,16 +467,16 @@ Test if `x` is a positive integer. Function signature: ``` -(neg-int? [x]) +(neg-int? ([x])) ``` -Test if `x` is a negetive integer. +Test if `x` is a negative integer. ## `double?` Function signature: ``` -(double? [x]) +(double? ([x])) ``` Test if `x` is a number with floating point data. @@ -510,7 +485,7 @@ Test if `x` is a number with floating point data. Function signature: ``` -(empty? [x]) +(empty? ([x])) ``` Check if collection is empty. @@ -519,7 +494,7 @@ Check if collection is empty. Function signature: ``` -(not-empty [x]) +(not-empty ([x])) ``` If `x` is empty, returns `nil`, otherwise `x`. @@ -528,7 +503,7 @@ If `x` is empty, returns `nil`, otherwise `x`. Function signature: ``` -(vector [& args]) +(vector ([& args])) ``` Constructs sequential table out of it's arguments. @@ -546,7 +521,7 @@ Sets additional metadata for function [`vector?`](#vector?) to work. Function signature: ``` -(seq [col]) +(seq ([col])) ``` Create sequential table. @@ -554,7 +529,8 @@ Create sequential table. Transforms original table to sequential table of key value pairs stored as sequential tables in linear time. If `col` is an associative table, returns sequential table of vectors with key and -value. If `col` is sequential table, returns its shallow copy. +value. If `col` is sequential table, returns its shallow copy. If +`col` is string, return sequential table of its codepoints. ### Examples Sequential tables remain as is: @@ -585,16 +561,16 @@ Additionally you can use [`conj`](#conj) and [`apply`](#apply) with Function signature: ``` -(kvseq [tbl]) +(kvseq ([col])) ``` -Transforms any table kind to key-value sequence. +Transforms any table to key-value sequence. ## `first` Function signature: ``` -(first [col]) +(first ([col])) ``` Return first element of a table. Calls `seq` on its argument. @@ -603,7 +579,7 @@ Return first element of a table. Calls `seq` on its argument. Function signature: ``` -(rest [col]) +(rest ([col])) ``` Returns table of all elements of a table but the first one. Calls @@ -613,7 +589,7 @@ Returns table of all elements of a table but the first one. Calls Function signature: ``` -(last [col]) +(last ([col])) ``` Returns the last element of a table. Calls `seq` on its argument. @@ -622,7 +598,7 @@ Returns the last element of a table. Calls `seq` on its argument. Function signature: ``` -(butlast [col]) +(butlast ([col])) ``` Returns everything but the last element of a table as a new @@ -632,10 +608,7 @@ Returns everything but the last element of a table as a new Function signature: ``` -(conj - ([tbl]) - ([tbl x]) - ([tbl x & xs])) +(conj ([]) ([tbl]) ([tbl x]) ([tbl x & xs])) ``` Insert `x` as a last element of a table `tbl`. @@ -680,10 +653,7 @@ See [`hash-map`](#hash-map) for creating empty associative tables. Function signature: ``` -(disj - ([s]) - ([s k]) - ([s k & ks])) +(disj ([s]) ([s k]) ([s k & ks])) ``` Remove key `k` from set `s`. @@ -692,7 +662,7 @@ Remove key `k` from set `s`. Function signature: ``` -(cons [x tbl]) +(cons ([x tbl])) ``` Insert `x` to `tbl` at the front. Calls [`seq`](#seq) on `tbl`. @@ -701,10 +671,7 @@ Insert `x` to `tbl` at the front. Calls [`seq`](#seq) on `tbl`. Function signature: ``` -(concat - ([x]) - ([x y]) - ([x y & xs])) +(concat ([]) ([x]) ([x y]) ([x y & xs])) ``` Concatenate tables. @@ -713,9 +680,7 @@ Concatenate tables. Function signature: ``` -(reduce - ([f col]) - ([f val col])) +(reduce ([f col]) ([f val col])) ``` Reduce collection `col` using function `f` and optional initial value `val`. @@ -747,7 +712,7 @@ Reduce sequence of numbers with [`add`](#add) Function signature: ``` -(reduced [x]) +(reduced ([x])) ``` Wraps `x` in such a way so [`reduce`](#reduce) will terminate early @@ -779,7 +744,7 @@ valid number, but we've terminated right before we've reached it. Function signature: ``` -(reduce-kv [f val tbl]) +(reduce-kv ([f val tbl])) ``` Reduces an associative table using function `f` and initial value `val`. @@ -869,7 +834,7 @@ Basic `zipmap` implementation: Function signature: ``` -(filter [pred col]) +(filter ([pred col])) ``` Returns a sequential table of the items in `col` for which `pred` @@ -879,7 +844,7 @@ Returns a sequential table of the items in `col` for which `pred` Function signature: ``` -(every? [pred tbl]) +(every? ([pred tbl])) ``` Test if every item in `tbl` satisfies the `pred`. @@ -888,7 +853,7 @@ Test if every item in `tbl` satisfies the `pred`. Function signature: ``` -(some [pred tbl]) +(some ([pred tbl])) ``` Test if any item in `tbl` satisfies the `pred`. @@ -897,7 +862,7 @@ Test if any item in `tbl` satisfies the `pred`. Function signature: ``` -(not-any? [pred tbl]) +(not-any? ([pred tbl])) ``` Test if no item in `tbl` satisfy the `pred`. @@ -906,10 +871,7 @@ Test if no item in `tbl` satisfy the `pred`. Function signature: ``` -(range - ([upper]) - ([lower upper]) - ([lower upper step])) +(range ([upper]) ([lower upper]) ([lower upper step])) ``` return range of of numbers from `lower` to `upper` with optional `step`. @@ -918,7 +880,7 @@ return range of of numbers from `lower` to `upper` with optional `step`. Function signature: ``` -(reverse [tbl]) +(reverse ([tbl])) ``` Returns table with same items as in `tbl` but in reverse order. @@ -927,7 +889,7 @@ Returns table with same items as in `tbl` but in reverse order. Function signature: ``` -(identity [x]) +(identity ([x])) ``` Returns its argument. @@ -936,10 +898,7 @@ Returns its argument. Function signature: ``` -(comp - ([f]) - ([f g]) - ([f g & fs])) +(comp ([]) ([f]) ([f g]) ([f g & fs])) ``` Compose functions. @@ -948,7 +907,7 @@ Compose functions. Function signature: ``` -(complement [f]) +(complement ([f])) ``` Takes a function `f` and returns the function that takes the same @@ -959,7 +918,7 @@ oppisite truth value. Function signature: ``` -(constantly [x]) +(constantly ([x])) ``` Returns a function that takes any number of arguments and returns `x`. @@ -968,7 +927,7 @@ Returns a function that takes any number of arguments and returns `x`. Function signature: ``` -(memoize [f]) +(memoize ([f])) ``` Returns a memoized version of a referentially transparent function. @@ -981,9 +940,7 @@ use. Function signature: ``` -(assoc - ([tbl k v]) - ([tbl k v & kvs])) +(assoc ([tbl k v]) ([tbl k v & kvs])) ``` Associate key `k` with value `v` in `tbl`. @@ -992,8 +949,7 @@ Associate key `k` with value `v` in `tbl`. Function signature: ``` -(hash-map - ([& kvs])) +(hash-map ([]) ([& kvs])) ``` Create associative table from keys and values @@ -1002,9 +958,7 @@ Create associative table from keys and values Function signature: ``` -(get - ([tbl key]) - ([tbl key not-found])) +(get ([tbl key]) ([tbl key not-found])) ``` Get value from the table by accessing it with a `key`. @@ -1015,9 +969,7 @@ found in the table. Function signature: ``` -(get-in - ([tbl keys]) - ([tbl keys not-found])) +(get-in ([tbl keys]) ([tbl keys not-found])) ``` Get value from nested set of tables by providing key sequence. @@ -1028,7 +980,7 @@ found in the table. Function signature: ``` -(keys [tbl]) +(keys ([tbl])) ``` Returns a sequence of the table's keys, in the same order as [`seq`](#seq). @@ -1037,7 +989,7 @@ Returns a sequence of the table's keys, in the same order as [`seq`](#seq). Function signature: ``` -(vals [tbl]) +(vals ([tbl])) ``` Returns a sequence of the table's values, in the same order as [`seq`](#seq). @@ -1046,7 +998,7 @@ Returns a sequence of the table's values, in the same order as [`seq`](#seq). Function signature: ``` -(find [tbl key]) +(find ([tbl key])) ``` Returns the map entry for `key`, or `nil` if key not present. @@ -1055,10 +1007,7 @@ Returns the map entry for `key`, or `nil` if key not present. Function signature: ``` -(dissoc - ([tbl]) - ([tbl key]) - ([tbl key & keys])) +(dissoc ([tbl]) ([tbl key]) ([tbl key & keys])) ``` Remove `key` from table `tbl`. @@ -1067,7 +1016,7 @@ Remove `key` from table `tbl`. Function signature: ``` -(remove-method [multifn dispatch-val]) +(remove-method ([multifn dispatch-val])) ``` Remove method from `multifn` for given `dispatch-val`. @@ -1076,7 +1025,7 @@ Remove method from `multifn` for given `dispatch-val`. Function signature: ``` -(remove-all-methods [multifn]) +(remove-all-methods ([multifn])) ``` Removes all of the methods of multimethod @@ -1085,7 +1034,7 @@ Removes all of the methods of multimethod Function signature: ``` -(methods [multifn]) +(methods ([multifn])) ``` Given a multimethod, returns a map of dispatch values -> dispatch fns @@ -1094,7 +1043,7 @@ Given a multimethod, returns a map of dispatch values -> dispatch fns Function signature: ``` -(get-method [multifn dispatch-val]) +(get-method ([multifn dispatch-val])) ``` Given a multimethod and a dispatch value, returns the dispatch `fn` @@ -1104,7 +1053,7 @@ that would apply to that value, or `nil` if none apply and no default. Function signature: ``` -(ordered-set [& xs]) +(ordered-set ([& xs])) ``` Create ordered set. @@ -1118,7 +1067,7 @@ at the end of the set. Ordered set supports removal of items via `tset` and [`disj`](#disj). To add element to the ordered set use `tset` or [`conj`](#conj). Both operations modify the set. -**Note**: Hash set prints as `#{a b c}`, but this construct is not +**Note**: Hash set prints as `@set{a b c}`, but this construct is not supported by the Fennel reader, so you can't create sets with this syntax. Use `hash-set` function instead. @@ -1130,18 +1079,16 @@ be in the set: ``` fennel >> (ordered-set) -#{} +@set{} >> (ordered-set :a :c :b) -#{"a" "c" "b"} +@set{:a :c :b} ``` Duplicate items are not added: ``` fennel ->> (ordered-set) -#{} >> (ordered-set :a :c :a :a :a :a :c :b) -#{"a" "c" "b"} +@set{:a :c :b} ``` #### Check if set contains desired value: @@ -1151,9 +1098,9 @@ desired key will either return the key, or `nil`: ``` fennel >> (local oset (ordered-set [:a :b :c] [:c :d :e] :e :f)) >> (oset [:a :b :c]) -[:a :b :c] +["a" "b" "c"] >> (. oset :e) -:e +"e" >> (oset [:a :b :f]) nil ``` @@ -1164,8 +1111,7 @@ To add element to the set use [`conj`](#conj) or `tset` ``` fennel >> (local oset (ordered-set :a :b :c)) >> (conj oset :d :e) ->> oset -#{"a" "b" "c" "d" "e"} +@set{:a :b :c :d :e} ``` ##### Remove items from the set: @@ -1174,11 +1120,10 @@ To add element to the set use [`disj`](#disj) or `tset` ``` fennel >> (local oset (ordered-set :a :b :c)) >> (disj oset :b) ->> oset -#{"a" "c"} +@set{:a :c} >> (tset oset :a nil) >> oset -#{"c"} +@set{:c} ``` #### Equality semantics @@ -1199,7 +1144,7 @@ true Function signature: ``` -(hash-set [& xs]) +(hash-set ([& xs])) ``` Create hash set. @@ -1213,7 +1158,7 @@ using [`conj`](#con) or `tset` functions, and items can be removed with [`disj`](#disj) or `tset` functions. Rest semantics are the same as for [`ordered-set`](#ordered-set) -**Note**: Hash set prints as `#{a b c}`, but this construct is not +**Note**: Hash set prints as `@set{a b c}`, but this construct is not supported by the Fennel reader, so you can't create sets with this syntax. Use `hash-set` function instead. @@ -1225,5 +1170,5 @@ Copyright (C) 2020 Andrey Orst License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) - diff --git a/doc/macros.md b/doc/macros.md new file mode 100644 index 0000000..e9cc517 --- /dev/null +++ b/doc/macros.md @@ -0,0 +1,588 @@ +# 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) +- [`deep-tostring`](#deep-tostring) + +## `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. +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`. + +## `deep-tostring` +Function signature: + +``` +(deep-tostring data key?) +``` + +**Undocumented** + + +--- + +Copyright (C) 2020 Andrey Orst + +License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) + + + diff --git a/doc/tests/test.md b/doc/tests/test.md index c338561..8a6c7e5 100644 --- a/doc/tests/test.md +++ b/doc/tests/test.md @@ -94,5 +94,5 @@ Function signature: Assert for not truth. Works the same as [`assert-is`](#assert-is). - -- cgit v1.2.3