summaryrefslogtreecommitdiff
path: root/doc/macros.md
diff options
context:
space:
mode:
authorAndrey Listopadov <andreyorst@gmail.com>2022-08-21 21:25:30 +0300
committerAndrey Listopadov <andreyorst@gmail.com>2022-08-21 21:25:30 +0300
commit3f738c3368ddaadbaa4372443cece90651d1ccd2 (patch)
tree4ebcceeecdd01e8375b7b7f0a7172b2fe2620f09 /doc/macros.md
parent9bbe5ddf93c7c8b17a73318bc89dd1330f4f3f59 (diff)
update docs, readme and cnagelog for v1.0.0 release
Diffstat (limited to 'doc/macros.md')
-rw-r--r--doc/macros.md609
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 -->