diff options
| author | Andrey Listopadov <andreyorst@gmail.com> | 2022-08-21 21:25:30 +0300 |
|---|---|---|
| committer | Andrey Listopadov <andreyorst@gmail.com> | 2022-08-21 21:25:30 +0300 |
| commit | 3f738c3368ddaadbaa4372443cece90651d1ccd2 (patch) | |
| tree | 4ebcceeecdd01e8375b7b7f0a7172b2fe2620f09 /doc/macros.md | |
| parent | 9bbe5ddf93c7c8b17a73318bc89dd1330f4f3f59 (diff) | |
update docs, readme and cnagelog for v1.0.0 release
Diffstat (limited to 'doc/macros.md')
| -rw-r--r-- | doc/macros.md | 609 |
1 files changed, 197 insertions, 412 deletions
diff --git a/doc/macros.md b/doc/macros.md index c90d0af..bac17f9 100644 --- a/doc/macros.md +++ b/doc/macros.md @@ -1,310 +1,44 @@ -# Macros (v0.5.4) -Macros for Cljlib that implement various facilities from Clojure. +# Macros (v1.0.0) +Macros for fennel-cljlib. **Table of contents** -- [`fn*`, `defn`](#fn-defn) -- [`try`](#try) +- [`cond`](#cond) - [`def`](#def) -- [`defonce`](#defonce) -- [`defmulti`](#defmulti) - [`defmethod`](#defmethod) -- [`into`](#into) -- [`empty`](#empty) -- [`with-meta`](#with-meta) -- [`meta`](#meta) +- [`defmulti`](#defmulti) +- [`defn`](#defn) +- [`defn-`](#defn-) +- [`fn*`](#fn) - [`if-let`](#if-let) -- [`when-let`](#when-let) - [`if-some`](#if-some) -- [`when-some`](#when-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) -## `fn*`, `defn` -Function signature: - -``` -(fn* name docstring? ([arglist*] body)*) -``` - -Create (anonymous) function of fixed arity. -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: - -``` 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 ((or table.unpack _G.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) ((or table.unpack _G.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) ((or table.unpack _G.unpack) tables)))) ;; call to `join` resolves to ns.tables.join - -(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` -from `ns.strings`, so the latter must be fully qualified -`ns.strings.join` when called outside of the function. - -## `try` +## `cond` 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`. - -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) +(cond ...) ``` +**Undocumented** ## `def` Function signature: ``` -(def attr-map? name expr) -``` - -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. - -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) - -(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`](#defonce) -calls will not override existing bindings. Accepts same `attr-map?` as -[`def`](#def), and sets `name` to the result of `expr`: - -``` fennel -(defonce a 10) -(defonce a 20) -(assert-eq a 10) -``` - -## `defmulti` -Function signature: - -``` -(defmulti name docstring? dispatch-fn options*) +(def ...) ``` -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. +**Undocumented** ## `defmethod` Function signature: @@ -328,6 +62,8 @@ 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) @@ -343,6 +79,8 @@ were found for given dispatch value. Multi-arity function tails are also supported: ``` fennel +(ns test) + (defmulti foo (fn* ([x] [x]) ([x y] [x y]))) (defmethod foo [10] [_] (print "I've knew I'll get 10")) @@ -363,6 +101,8 @@ 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)) @@ -391,186 +131,124 @@ 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` +## `defmulti` Function signature: ``` -(into to from) -``` - -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 -to [`into`](#into) this will have different effects for associative tables and -vectors: - -``` fennel -(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 -(assert-eq (into [] {:a 1}) [[:a 1]]) -(assert-eq (into {} [[:a 1] [:b 2]]) {:a 1 :b 2}) +(defmulti name docstring? dispatch-fn options*) ``` -Same rules apply to runtime detection of table type, except that this -will not work for empty tables: - -``` fennel -(local empty-table {}) -(assert-eq (into empty-table {:a 1}) [[:a 1]]) -``` fennel - -If table is empty, [`into`](#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: +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`. -``` fennel -(local t1 [1 2 3]) -(local t2 {:a 10 :c 3}) -(assert-eq (into t1 {:a 1}) [1 2 3 [:a 1]]) -(assert-eq (into t2 {:a 1}) {:a 1 :c 3}) -``` +Accepts optional `docstring?`, and `options*` arguments, where +`options*` is a sequence of key value pairs representing additional +attributes. Supported options: -`cljlib.fnl` module provides two additional functions `vector` and -`hash-map`, that can create empty tables, which can be distinguished -at runtime: +`:default` - the default dispatch value, defaults to `:default`. -``` fennel -(assert-eq (into (vector) {:a 1}) [[:a 1]]) -(assert-eq (into (hash-map) [[:a 1] [:b 2]]) {:a 1 :b 2}) -``` +By default, multifunction has no multimethods, see +[`defmethod`](#defmethod) on how to add one. -## `empty` +## `defn` Function signature: ``` -(empty x) +(defn ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) ``` -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))) +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. -(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. - -## `with-meta` +## `defn-` Function signature: ``` -(with-meta value meta) +(defn- ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) ``` -Attach [`meta`](#meta) to a `value`. - -``` 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 -``` +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. -## `meta` +## `fn*` Function signature: ``` -(meta value) +(fn* ([name doc-string? [params*] pre-post? body]) ([name doc-string? ([params*] pre-post? body) +])) ``` -Get `value` metadata. If value has no metadata returns `nil`. - -### Example +Clojure-inspired `fn` macro for defining functions. +Supports multi-arity dispatching via the following syntax: -``` fennel -(meta (with-meta {} {:meta "data"})) -;; => {:meta "data"} -``` +(fn* optional-name + optional-docstring + ([arity1] body1) + ([other arity2] body2)) -### Note -There are several important gotchas about using metadata. +Accepts pre and post conditions in a form of a table after argument +list: -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. +(fn* optional-name + optional-docstring + [arg1 arg2] + {:pre [(check1 arg1 arg2) (check2 arg1)] + :post [(check1 $) ... (checkN $)]} + body) -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`. +The same syntax applies to multi-arity version. -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`. +(pre and post checks are not yet implemented) ## `if-let` Function signature: ``` -(if-let [binding test] then-branch else-branch) +(if-let [name test] if-branch 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`. +**Undocumented** -## `when-let` +## `if-some` Function signature: ``` -(when-let [binding test] & body) +(if-some [name test] if-branch else-branch ...) ``` -If `binding` was bound by `test` to logical true, evaluates `body` in -implicit `do`. +**Undocumented** -## `if-some` +## `in-ns` Function signature: ``` -(if-some [binding test] then-branch else-branch) +(in-ns name) ``` -If `test` is non-`nil`, evaluates `then-branch` with `binding`-form bound -to the value of test, if not, yields `else-branch`. +**Undocumented** -## `when-some` +## `lazy-cat` Function signature: ``` -(when-some [binding test] & body) +(lazy-cat ...) ``` -If `test` sets `binding` to non-`nil`, evaluates `body` in implicit -`do`. +**Undocumented** + +## `lazy-seq` +Function signature: +``` +(lazy-seq ...) +``` + +**Undocumented** ## `loop` Function signature: @@ -581,8 +259,9 @@ Function signature: 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*`. +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. @@ -608,6 +287,112 @@ 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) +## `ns` +Function signature: + +``` +(ns name commentary requirements) +``` + +**Undocumented** + +## `time` +Function signature: + +``` +(time expr) +``` + +Measure expression execution time in ms. + +## `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`. + +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] ...) +``` + +**Undocumented** + +## `when-some` +Function signature: + +``` +(when-some [name test] ...) +``` + +**Undocumented** + --- @@ -616,5 +401,5 @@ Copyright (C) 2020-2021 Andrey Listopadov License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) -<!-- Generated with Fenneldoc v0.1.6 +<!-- Generated with Fenneldoc v0.1.9 https://gitlab.com/andreyorst/fenneldoc --> |