From b22f270b596881630fb1dbd6a721c1fe6312f00d Mon Sep 17 00:00:00 2001 From: Andrey Orst Date: Sun, 24 Jan 2021 16:22:57 +0300 Subject: feature: include documentation testing in pipeline --- doc/macros.md | 207 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 116 insertions(+), 91 deletions(-) (limited to 'doc/macros.md') diff --git a/doc/macros.md b/doc/macros.md index 1841b14..f1efa48 100644 --- a/doc/macros.md +++ b/doc/macros.md @@ -1,4 +1,4 @@ -# Macros.fnl (0.3.0) +# Macros.fnl (0.4.0) Macros for Cljlib that implement various facilities from Clojure. **Table of contents** @@ -27,7 +27,10 @@ Function signature: ``` Create (anonymous) function of fixed arity. -Supports multiple arities by defining bodies as lists. +Accepts optional `name` and `docstring?` as first two arguments, +followed by single or multiple arity bodies defined as lists. Each +list starts with `arglist*` vector, which supports destructuring, and +is followed by `body*` wrapped in implicit `do`. ### Examples Named function of fixed arity 2: @@ -136,12 +139,12 @@ namespace tables: ([t1 t2 & tables] (join (join t1 t2) ((or table.unpack _G.unpack) tables)))) ;; call to `join` resolves to ns.tables.join -(ns.strings.join "a" "b" "c") -;; => abc -(join ["a"] ["b"] ["c"] ["d" "e"]) -;; => ["a" "b" "c" "d" "e"] -(join "a" "b" "c") -;; {} +(assert-eq (ns.strings.join "a" "b" "c") "abc") + +(assert-eq (join ["a"] ["b"] ["c"] ["d" "e"]) + ["a" "b" "c" "d" "e"]) +(assert-eq (join "a" "b" "c") + []) ``` Note that this creates a collision and local `join` overrides `join` @@ -159,10 +162,12 @@ General purpose try/catch/finally macro. Wraps its body in `pcall` and checks the return value with `match` macro. -Catch clause is written either as (catch symbol body*), thus acting as -catch-all, or (catch value body*) for catching specific errors. It is -possible to have several `catch` clauses. If no `catch` clauses -specified, an implicit catch-all clause is created. +Catch clause is written either as `(catch symbol body*)`, thus acting +as catch-all, or `(catch value body*)` for catching specific errors. +It is possible to have several `catch` clauses. If no `catch` clauses +specified, an implicit catch-all clause is created. `body*`, and +inner expressions of `catch-clause*`, and `finally-clause?` are +wrapped in implicit `do`. Finally clause is optional, and written as (finally body*). If present, it must be the last clause in the `try` form, and the only @@ -184,36 +189,36 @@ Catch all errors, ignore those and return fallback value: (+ x y) (catch _ 0))) -(add nil 1) ;; => 0 +(assert-eq (add nil 1) 0) ``` Catch error and do cleanup: ``` fennel -(let [tbl []] - (try - (table.insert tbl "a") - (table.insert tbl "b" "c") - (catch _ - (each [k _ (pairs tbl)] - (tset tbl k nil)))) - tbl) -;; => {} +(local tbl []) + +(try + (table.insert tbl "a") + (table.insert tbl "b" "c") + (catch _ + (each [k _ (pairs tbl)] + (tset tbl k nil)))) + +(assert-eq (length tbl) 0) + ``` Always run some side effect action: ``` fennel -(local res (try 10 (finally (print "side-effect!")))) -;; => side-effect! -;; => nil -res -;; => 10 -(local res (try (error 10) (catch 10 nil) (finally (print "side-effect!")))) -;; => side-effect! -;; => nil -res -;; => nil +(local t []) +(local res (try 10 (finally (table.insert t :finally)))) +(assert-eq (. t 1) :finally) +(assert-eq res 10) + +(local res (try (error 10) (catch 10 nil) (finally (table.insert t :again)))) +(assert-eq (. t 2) :again) +(assert-eq res nil) ``` @@ -224,15 +229,22 @@ Function signature: (def attr-map? name expr) ``` -Wrapper around `local` which can -declare variables inside namespace, and as local at the same time -similarly to [`fn*`](#fn*): +Wrapper around `local` which can declare variables inside namespace, +and as local `name` at the same time similarly to +[`fn*`](#fn*). Accepts optional `attr-map?` which can contain a +docstring, and whether variable should be mutable or not. Sets +variable to the result of `expr`. ``` fennel (def ns {}) (def a 10) ;; binds `a` to `10` +(assert-eq a 10) + (def ns.b 20) ;; binds `ns.b` and `b` to `20` + +(assert-eq b 20) +(assert-eq ns.b 20) ``` `a` is a `local`, and both `ns.b` and `b` refer to the same value. @@ -263,42 +275,48 @@ Function signature: ``` Works the same as [`def`](#def), but ensures that later `defonce` -calls will not override existing bindings: +calls will not override existing bindings. Accepts same `attr-map?` as +`def`, and sets `name` to the result of `expr`: ``` fennel (defonce a 10) (defonce a 20) -(print a) ;; => prints 10 +(assert-eq a 10) ``` ## `defmulti` Function signature: ``` -(defmulti name docstring? dispatch-fn attr-map?) +(defmulti name docstring? dispatch-fn options*) ``` -Create multifunction with -runtime dispatching based on results from `dispatch-fn`. Returns an -empty table with `__call` metamethod, that calls `dispatch-fn` on its -arguments. Amount of arguments passed, should be the same as accepted -by `dispatch-fn`. Looks for multimethod based on result from -`dispatch-fn`. +Create multifunction `name` with runtime dispatching based on results +from `dispatch-fn`. Returns a proxy table with `__call` metamethod, +that calls `dispatch-fn` on its arguments. Amount of arguments +passed, should be the same as accepted by `dispatch-fn`. Looks for +multimethod based on result from `dispatch-fn`. + +Accepts optional `docstring?`, and `options*` arguments, where +`options*` is a sequence of key value pairs representing additional +attributes. Supported options: + +`:default` - the default dispatch value, defaults to `:default`. By default, multifunction has no multimethods, see -[`multimethod`](#multimethod) on how to add one. +[`defmethod`](#defmethod) on how to add one. ## `defmethod` Function signature: ``` -(defmethod multifn dispatch-val fnspec) +(defmethod multi-fn dispatch-value fnspec) ``` -Attach new method to multi-function dispatch value. accepts the `multi-fn` -as its first argument, the dispatch value as second, and function tail -starting from argument list, followed by function body as in -[`fn*`](#fn). +Attach new method to multi-function dispatch value. accepts the +`multi-fn` as its first argument, the `dispatch-value` as second, and +`fnspec` - a function tail starting from argument list, followed by +function body as in [`fn*`](#fn). ### Examples Here are some examples how multimethods can be used. @@ -315,7 +333,7 @@ to another multimethod: (defmethod fac 0 [_] 1) (defmethod fac :default [x] (* x (fac (- x 1)))) -(fac 4) ;; => 24 +(assert-eq (fac 4) 24) ``` `:default` is a special method which gets called when no other methods @@ -348,22 +366,30 @@ tables to Lua's one: (defmulti to-lua-str (fn [x] (type x))) (defmethod to-lua-str :number [x] (tostring x)) -(defmethod to-lua-str :table [x] (let [res []] - (each [k v (pairs x)] - (table.insert res (.. "[" (to-lua-str k) "] = " (to-lua-str v)))) - (.. "{" (table.concat res ", ") "}"))) +(defmethod to-lua-str :table [x] + (let [res []] + (each [k v (pairs x)] + (table.insert res (.. "[" (to-lua-str k) "] = " (to-lua-str v)))) + (.. "{" (table.concat res ", ") "}"))) (defmethod to-lua-str :string [x] (.. "\"" x "\"")) (defmethod to-lua-str :default [x] (tostring x)) -(print (to-lua-str {:a {:b 10}})) -;; => {["a"] = {["b"] = 10}} +(assert-eq (to-lua-str {:a {:b 10}}) "{[\"a\"] = {[\"b\"] = 10}}") -(print (to-lua-str [:a :b :c [:d {:e :f}]])) -;; => {[1] = "a", [2] = "b", [3] = "c", [4] = {[1] = "d", [2] = {["e"] = "f"}}} +(assert-eq (to-lua-str [:a :b :c [:d {:e :f}]]) + "{[1] = \"a\", [2] = \"b\", [3] = \"c\", [4] = {[1] = \"d\", [2] = {[\"e\"] = \"f\"}}}") ``` And if we call it on some table, we'll get a valid Lua table, which we -can then reformat as we want and use in Lua if we want. +can then reformat as we want and use in Lua. + +All of this can be done with functions, and single entry point +function, that uses if statement and branches on the type, however one +of the additional features of multimethods, is that separate libraries +can extend such multimethod by adding additional claues to it without +needing to patch the source of the function. For example later on +support for userdata or coroutines can be added to `to-lua-str` +function as a separate multimethods for respective types. ## `into` Function signature: @@ -372,7 +398,7 @@ Function signature: (into to from) ``` -Transform one table into another. Mutates first table. +Transform table `from` into another table `to`. Mutates first table. Transformation happens in runtime, but type deduction happens in compile time if possible. This means, that if literal values passed @@ -380,15 +406,15 @@ to `into` this will have different effects for associative tables and vectors: ``` fennel -(into [1 2 3] [4 5 6]) ;; => [1 2 3 4 5 6] -(into {:a 1 :c 2} {:a 0 :b 1}) ;; => {:a 0 :b 1 :c 2} +(assert-eq (into [1 2 3] [4 5 6]) [1 2 3 4 5 6]) +(assert-eq (into {:a 1 :c 2} {:a 0 :b 1}) {:a 0 :b 1 :c 2}) ``` Conversion between different table types is also supported: ``` fennel -(into [] {:a 1 :b 2 :c 3}) ;; => [[:a 1] [:b 2] [:c 3]] -(into {} [[:a 1] [:b 2]]) ;; => {:a 1 :b 2} +(assert-eq (into [] {:a 1}) [[:a 1]]) +(assert-eq (into {} [[:a 1] [:b 2]]) {:a 1 :b 2}) ``` Same rules apply to runtime detection of table type, except that this @@ -396,7 +422,7 @@ will not work for empty tables: ``` fennel (local empty-table {}) -(into empty-table {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] +(assert-eq (into empty-table {:a 1}) [[:a 1]]) ``` fennel If table is empty, `into` defaults to sequential table, because it @@ -408,8 +434,8 @@ runtime, and this works as expected: ``` fennel (local t1 [1 2 3]) (local t2 {:a 10 :c 3}) -(into t1 {:a 1 :b 2}) ;; => [1 2 3 [:a 1] [:b 2]] -(into t2 {:a 1 :b 2}) ;; => {:a 1 :b 2 :c 3} +(assert-eq (into t1 {:a 1}) [1 2 3 [:a 1]]) +(assert-eq (into t2 {:a 1}) {:a 1 :c 3}) ``` `cljlib.fnl` module provides two additional functions `vector` and @@ -417,8 +443,8 @@ runtime, and this works as expected: at runtime: ``` fennel -(into (vector) {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] -(into (hash-map) [[:a 1 :b 2]]) ;; => {:a 1 :b 2} +(assert-eq (into (vector) {:a 1}) [[:a 1]]) +(assert-eq (into (hash-map) [[:a 1] [:b 2]]) {:a 1 :b 2}) ``` ## `empty` @@ -442,10 +468,10 @@ and return result of the same type: (table.insert res (f v))) (into (empty tbl) res))) -(map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3}) -;; => {:A 1 :B 2 :C 3} -(map #(* $ $) [1 2 3 4]) -;; [1 4 9 16] +(assert-eq (map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3}) + {:A 1 :B 2 :C 3}) +(assert-eq (map #(* $ $) [1 2 3 4]) + [1 4 9 16]) ``` See [`into`](#into) for more info on how conversion is done. @@ -456,11 +482,11 @@ Function signature: (when-meta [& body]) ``` -Wrapper that compiles away if metadata support was not enabled. What -this effectively means, is that everything that is wrapped with this -macro will disappear from the resulting Lua code if metadata is not -enabled when compiling with `fennel --compile` without `--metadata` -switch. +Wrapper that compiles away if metadata support was not enabled. +What this effectively means, is that everything that is wrapped with +this macro and its `body` will disappear from the resulting Lua code +if metadata is not enabled when compiling with `fennel --compile` +without `--metadata` switch. ## `with-meta` Function signature: @@ -469,7 +495,7 @@ Function signature: (with-meta value meta) ``` -Attach metadata to a value. When metadata feature is not enabled, +Attach `meta` to a `value`. When metadata feature is not enabled, returns the value without additional metadata. ``` fennel @@ -526,9 +552,9 @@ Function signature: (if-let [binding test] then-branch else-branch) ``` -If test is logical true, -evaluates `then-branch` with binding-form bound to the value of test, -if not, yields `else-branch`. +If `binding` is set by `test` to logical true, evaluates `then-branch` +with binding-form bound to the value of test, if not, yields +`else-branch`. ## `when-let` Function signature: @@ -537,8 +563,8 @@ Function signature: (when-let [binding test] & body) ``` -If test is logical true, -evaluates `body` in implicit `do`. +If `binding` was bound by `test` to logical true, evaluates `body` in +implicit `do`. ## `if-some` Function signature: @@ -547,9 +573,8 @@ Function signature: (if-some [binding test] then-branch else-branch) ``` -If test is non-`nil`, evaluates -`then-branch` with binding-form bound to the value of test, if not, -yields `else-branch`. +If `test` is non-`nil`, evaluates `then-branch` with `binding`-form bound +to the value of test, if not, yields `else-branch`. ## `when-some` Function signature: @@ -558,8 +583,8 @@ Function signature: (when-some [binding test] & body) ``` -If test is non-`nil`, -evaluates `body` in implicit `do`. +If `test` sets `binding` to non-`nil`, evaluates `body` in implicit +`do`. --- @@ -569,5 +594,5 @@ Copyright (C) 2020 Andrey Orst License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) - -- cgit v1.2.3