diff options
| author | Andrey Orst <andreyorst@gmail.com> | 2020-11-14 16:51:51 +0300 |
|---|---|---|
| committer | Andrey Orst <andreyorst@gmail.com> | 2020-11-14 16:55:11 +0300 |
| commit | 4ea70bf39971eeb742a5de689a9ad8ba63559ecb (patch) | |
| tree | 0c8268316eff16ca5b066063b29ea410c0405336 | |
| parent | 612d8185384ca77347ed89d02c45b91cd9dcd57a (diff) | |
Feature(doc): upload autogenerated documentation.
| -rw-r--r-- | README.org | 517 | ||||
| -rw-r--r-- | cljlib-macros.fnl | 24 | ||||
| -rw-r--r-- | cljlib.fnl | 157 | ||||
| -rw-r--r-- | doc/0.1.0/cljlib-macros.md | 288 | ||||
| -rw-r--r-- | doc/0.1.0/cljlib.md | 674 | ||||
| -rw-r--r-- | tests/fn.fnl | 14 |
6 files changed, 1064 insertions, 610 deletions
@@ -11,519 +11,4 @@ Even though it is project is experimental, the goals of this project are: - Be close to the platform, e.g. implement functions in a way that is efficient to use in Lua VM, - Be well documented library, with good test coverage. - -* Macros -List of macros provided by the library. - -** Metadata macros -Metadata in Fennel is a pretty tough subject, as there's no such thing as metadata in Lua. -Therefore, the metadata usage in Fennel is more limited compared to Clojure. -This library provides some facilities for metadata management, which are experimental and should be used with care. - -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=. - -*** =when-meta= -This macros is a 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=. - -*** =with-meta= -Attach metadata to a value. - -#+begin_src 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 -#+end_src - -When metadata feature is not enabled, returns the value without additional metadata. - -*** =meta= -Get metadata table from object: - -#+begin_src fennel - >> (meta (with-meta {} {:meta "data"})) - { - :meta "data" - } -#+end_src - -** =def= and =defonce= -=def= is wrappers around =local= which can declare variables inside namespace, and as local at the same time: - -#+begin_src fennel - >> (def ns {}) - >> (def a 10) - >> a - 10 - >> (def ns.a 20) - >> a - 20 - >> ns.a - 20 -#+end_src - -Both =ns.a= and =a= refer to the same value. - -=defonce= ensures that the binding isn't overridden by another =defonce=: - -#+begin_src fennel - >> (defonce ns {}) - >> (defonce ns.a 42) - >> (defonce ns 10) - >> ns - {:a 42} - >> a - 42 -#+end_src - -Both =def= and =defonce= support literal metadata table as first argument, or a :dynamic keyword, that uses Fennel =var= instead of =local=: - -#+begin_src fennel - >> (def {:dynamic true} a 10) - >> (set a 20) - >> a - 20 - >> (defonce :dynamic b 40) - >> (set b 42) - >> b - 42 -#+end_src - -Documentation string can be attached to value via =:doc= keyword. -However it is not recommended to attach metadata to everything except tables and functions: - -#+begin_src fennel - ;; Bad, may overlap with existing documentation for 299792458, if any - >> (def {:doc "The speed of light in m/s"} c 299792458) - >> (doc c) - c - The speed of light in m/s - - ;; OK - >> (def {:doc "default connection options"} - defaults {:port 1234 - :host localhost}) -#+end_src - -** =fn*= -Clojure's =fn= equivalent. -Returns a function of fixed amount of arguments by doing runtime dispatch based on argument count. -Capable of producing multi-arity functions: - -#+begin_src fennel - (fn* square "square number" [x] (^ x 2)) - - (square 9) ;; => 81.0 - (square 1 2) ;; => error - - (fn* range - "Returns increasing sequence of numbers from `lower' to `upper'. - If `lower' is not provided, sequence starts from zero. - Accepts optional `step'" - ([upper] (range 0 upper 1)) - ([lower upper] (range lower upper 1)) - ([lower upper step] - (let [res []] - (for [i lower (- upper step) step] - (table.insert res i)) - res))) - - (range 10) ;; => [0 1 2 3 4 5 6 7 8 9] - (range -10 0) ;; => [-10 -9 -8 -7 -6 -5 -4 -3 -2 -1] - (range 0 1 0.2) ;; => [0.0 0.2 0.4 0.6 0.8] -#+end_src - -Both variants support up to one arity with =& more=: - -#+begin_src fennel - (fn* vec [& xs] xs) - - (vec 1 2 3) ;; => [1 2 3] - - (fn* add - "sum two or more values" - ([] 0) - ([a] a) - ([a b] (+ a b)) - ([a b & more] (add (+ a b) (unpack more)))) - - (add) ;; => 0 - (add 1) ;; => 1 - (add 1 2) ;; => 3 - (add 1 2 3 4) ;; => 10 -#+end_src - -One extra capability of =fn*= supports the same semantic as =def= regarding namespaces: - -#+begin_src fennel - (local ns {}) - - (fn* ns.plus - ([] 0) - ([x] x) - ([x y] (+ x y)) - ([x y & zs] (apply plus (+ x y) zs))) - - ns -#+end_src - -Note, that =plus= is used without =ns= part, e.g. not =ns.plus=. -If we =require= this code from file in the REPL, we will see that our =ns= has single function =plus=: - -#+begin_src fennel - >> (local ns (require :module)) - >> ns - {add #<function 0xbada55code>} -#+end_src - -This is possible because =fn*= separates the namespace part from the function name, and creates a =local= variable with the same name as function, then defines the function within lexical scope of =do=, sets =namespace.foo= to it and returns the function object to the outer scope. - -#+begin_src fennel - (local plus - (do (fn plus [...] - ;; plus body - ) - (set ns.plus plus) - plus)) -#+end_src - -See =core.fnl= for more examples. - -** =fn+= -Works similarly to Fennel's =fn=, by creating ordinary function without arity semantics, except does the namespace automation like =fn*=, and has the same order of arguments as the latter: - -#+begin_src fennel - (local ns {}) - - ;; module & file-local functions - (fn+ ns.double - "double the number" - [x] - (* x 2)) - - (fn+ ns.triple - [x] - (* x 3)) - - ;; no namespace, file-local function - (fn+ quadruple - [x] - (* x 4)) - - ;; anonymous file-local function - (fn+ [x] (* x 5)) - - ns -#+end_src - -See =core.fnl= for more examples. - -** =if-let= and =when-let= -When test expression is not =nil= or =false=, evaluates the first body form with the =name= bound to the result of the expressions. - -#+begin_src fennel - (if-let [val (test)] - (print val) - :fail) -#+end_src - -Expanded form: - -#+begin_src fennel - (let [tmp (test)] - (if tmp - (let [val tmp] - (print val)) - :fail)) -#+end_src - -=when-let= is mostly the same, except doesn't have false branch and accepts any amount of forms: - -#+begin_src fennel - (when-let [val (test)] - (print val) - val) -#+end_src - -Expanded form: - -#+begin_src fennel - (let [tmp (test)] - (if tmp - (let [val tmp] - (print val) - val))) -#+end_src - -** =if-some= and =when-some= -Much like =if-let= and =when-let=, except tests expression for not being =nil=. - -#+begin_src fennel - (when-some [val (foo)] - (print (.. "val is not nil: " val)) - val) -#+end_src - -** =into= -Clojure's =into= function is implemented as macro, because Fennel has no runtime distinction between =[]= and ={}= tables, since Lua also doesn't feature this feature. -However we can do this at compile time. - -#+begin_src fennel - (into [1 2 3] [4 5 6]) ;; => [1 2 3 4 5 6] - (into [] {:a 1 :b 2 :c 3 :d 4}) ;; => [["d" 4] ["a" 1] ["b" 2] ["c" 3]] - (into {} [[:d 4] [:a 1] [:b 2] [:c 3]]) ;; => {:a 1 :b 2 :c 3 :d 4} - (into {:a 0 :e 5} {:a 1 :b 2 :c 3 :d 4}) ;; => {:a 1 :b 2 :c 3 :d 4 :e 5} -#+end_src - -Because the type check at compile time it will only respect the type when literal representation is used. -If a variable holding the table, its type is checked at runtime. -Empty tables default to sequential ones: - -#+begin_src fennel - (local a []) - (into a {:a 1 :b 2}) ;; => [["b" 2] ["a" 1]] - - (local b {}) - (into b {:a 1 :b 2}) ;; => [["b" 2] ["a" 1]] -#+end_src - -However, if target table is not empty, its type can be deduced: - -#+begin_src fennel - (local a {:c 3}) - (into a {:a 1 :b 2}) ;; => {:a 1 :b 2 :c 3} - - (local b [1]) - (into b {:a 1 :b 2}) ;; => [1 ["b" 2] ["a" 1]] -#+end_src - -Note that when converting associative table into sequential table order is determined by the =pairs= function. -Also note that if variable stores the table has both integer key 1, and other associative keys, the type will be the same as of sequential table. - -** =defmulti= and =defmethod= -A bit more simple implementations of Clojure's =defmulti= and =defmethod=. -=defmulti= macros returns an empty table with =__call= metamethod, that calls dispatching function on its arguments. -Methods are defined inside =multimethods= table, which is also stored in the metatable. - -=defmethod= adds a new method to the metatable of given =multifn=. -It accepts the multi-fn table as its first argument, the dispatch value as second, and Fennel's arglist followed by the body: - -#+begin_src fennel - (defmulti fac (fn [x] x)) - - (defmethod fac 0 [_] 1) - (defmethod fac :default [x] (* x (fac (- x 1)))) - - (fac 4) ;; => 24 -#+end_src - -=:default= is a special method which gets called when no other methods were found for given dispatch value. - - -* Functions -Here are some important functions from the library. -Full set can be examined by requiring the module. - -** =seq= -=seq= produces a sequential table from any kind of table in linear time. -Works mostly like in Clojure, but, since Fennel doesn't have list object, it returns sequential table or =nil=: - -#+begin_src fennel - (seq [1 2 3 4 5]) ;; => [1 2 3 4 5] - (seq {:a 1 :b 2 :c 3 :d 4}) - ;; => [["d" 4] ["a" 1] ["b" 2] ["c" 3]] - (seq []) ;; => nil - (seq {}) ;; => nil -#+end_src - -See =into= on how to transform such sequence back into associative table. - -** =first=, =last=, =butlast=, and =rest= -=first= returns first value of a table. -It call =seq= on it, so this takes linear time for any kind of table. -As a consequence, associative tables are supported: - -#+begin_src fennel - (first [1 2 3]) ;; => 1 - (first {:host "localhost" :port 2344 :options {}}) - ;; => ["host" "localhost"] -#+end_src - -=last= returns the last argument from table: - -#+begin_src fennel - (last [1 2 3]) ;; => 3 - (last {:a 1 :b 2}) ;; => [:b 2] -#+end_src - -=butlast= returns everything from the table, except the last item: - -#+begin_src fennel - (butlast [1 2 3]) ;; => [1 2] -#+end_src - -=rest= works the same way, but returns everything except first item of a table. - -#+begin_src fennel - (rest [1 2 3]) ;; => [2 3] - (rest {:host "localhost" :port 2344 :options {}}) - ;; => [["port" 2344] ["options" {}]] -#+end_src - -All these functions call =seq= on its argument, therefore expect everything to happen in linear time. -Because of that these functions are expensive, therefore should be avoided when table type is known beforehand, and the table can be manipulated with =.= or =get=. - -** =conj= and =cons= -Append and prepend item to the table. -Unlike Clojure, =conj=, and =cons= modify table passed to these functions. -This is done both to avoid copying of whole thing, and because Fennel doesn't have immutability guarantees. - -=cons= accepts value as its first argument and table as second, and puts value to the front of the table: - -#+begin_src fennel - (cons 1 [2 3]) ;; => [1 2 3] -#+end_src - -=conj= accepts table as its first argument and any amount of values afterwards. -It puts values in order given into the table: - -#+begin_src fennel - (conj [] 1 2 3) ;; => [1 2 3] -#+end_src - -It is also possible to add items to associative table: - -#+begin_src fennel - (conj {:a 1} [:b 2]) ;; => {:a 1 :b 2} - (conj {:a 1} [:b 2] [:a 0]) ;; => {:a 0 :b 2} -#+end_src - -Both functions return the resulting table, so it is possible to nest calls to both of these. -As an example, here's a classic map function: - -#+begin_src fennel - (fn map [f col] - (if-some [val (first col)] - (cons (f val) (map f (rest col))) - [])) -#+end_src - -=col= is not modified by the =map= function described above, but the =[]= table in the =else= branch of =is-some= is eventually modified by the stack of calls to =cons=. -However this library provides more efficient versions of map, that support arbitrary amount of tables. - -** =mapv= -Mapping function over table. -In Clojure we have a =seq= abstraction, that allows us to use single =mapv= on both vectors, and hash tables. -In this library the =seq= function is implemented in a similar way, so you can expect =mapv= to behave similarly to Clojure: - -#+begin_src fennel - (fn cube [x] (* x x x)) - (mapv cube [1 2 3]) ;; => [1 8 27] - - (mapv #(* $1 $2) [1 2 3] [1 -1 0]) ;; => [1 -2 0] - - (mapv (fn [f-name s-name company position] - (.. f-name " " s-name " works as " position " at " company)) - ["Bob" "Alice"] - ["Smith" "Watson"] - ["Happy Days co." "Coffee With You"] - ["secretary" "chief officer"]) - ;; => ["Bob Smith works as secretary at Happy Days co." - ;; "Alice Watson works as chief officer at Coffee With You"] - - (mapv (fn [[k v]] [(string.upper k) v]) {:host "localhost" :port 1344}) - ;; => [["HOST" "localhost"] ["PORT" 1344]] -#+end_src - -** =reduce= and =reduce-kv= -Ordinary reducing functions. -Work the same as in Clojure, except doesn't yield transducer when only function was passed. - -#+begin_src fennel - (fn add [a b] (+ a b)) - (reduce add [1 2 3 4 5]) ;; => 15 - (reduce add 10 [1 2 3 4 5]) ;; => 25 -#+end_src - -=reduce-kv= expects function that accepts 3 arguments and initial value. -Then it maps function over the associative map, by passing initial value as a first argument, key as second argument, and value as third argument. - -#+begin_src fennel - (reduce-kv (fn [acc key val] - (if (or (= key :a) (= key :c)) - (+ acc val) acc)) - 0 - {:a 10 :b -20 :c 10}) - ;; => 20 -#+end_src - -** Predicate functions -Set of functions, that are small but useful with =mapv= or =reduce=. -These are commonly used so it makes sense to have that, without defining via anonymous function or =#= shorthand every time. - -- =map?= - check if table is an associative table. - Returns =false= for empty table. -- =seq?= - check if table is a sequential table - Returns =false= for empty table. - -Other predicates are self-explanatory: - -- =assoc?= -- =boolean?= -- =double?= -- =empty?= -- =even?= -- =false?= -- =int?= -- =neg?= -- =nil?= -- =odd?= -- =pos?= -- =string?= -- =true?= -- =zero?= - -** =eq= -Deep compare values. -If given two tables, recursively calls =eq= on each field until one of the tables exhausted. -Other values are compared with default equality operator. - -** =comp= -Compose functions into one function. - -#+begin_src fennel - (fn square [x] (^ x 2)) - (fn inc [x] (+ x 1)) - - ((comp square inc) 5) ;; => 36 -#+end_src - -# LocalWords: Luajit VM arity runtime multi Cljlib fn mapv kv REPL -# LocalWords: namespaced namespace eq metatable Lua defonce arglist -# LocalWords: namespaces defmulti defmethod metamethod butlast -# LocalWords: prepend LocalWords docstring - -** =every?= and =not-any?= -=every?= checks if predicate is true for every item in the table. -=not-any?= checks if predicate is false foe every item in the table. - -#+begin_src fennel - >> (every? pos-int? [1 2 3 4]) - true - >> (not-any? pos-int? [-1 -2 -3 4.2]) - true -#+end_src +Documentation is autogenerated with [[https://gitlab.com/andreyorst/fenneldoc][Fenneldoc]] and can be found [[https://gitlab.com/andreyorst/fennel-cljlib/-/tree/master/doc][here]]. diff --git a/cljlib-macros.fnl b/cljlib-macros.fnl index e49713f..e62c185 100644 --- a/cljlib-macros.fnl +++ b/cljlib-macros.fnl @@ -74,18 +74,26 @@ Strings are transformed into a sequence of letters." (fn gen-arglist-doc [args] (if (list? (. args 1)) (let [arglist [] - open (if (> (length args) 1) "\n [" "") - close (if (= open "") "" "]")] + newline? (if (> (length args) 1) "\n (" "(")] (each [i v (ipairs args)] - (table.insert - arglist - (.. open (table.concat (gen-arglist-doc v) " ") close))) + (let [arglist-doc (gen-arglist-doc v)] + (when (next arglist-doc) + (table.insert + arglist + (.. newline? (table.concat arglist-doc " ") ")"))))) arglist) (sequence? (. args 1)) - (let [arglist []] - (each [_ v (ipairs (. args 1))] - (table.insert arglist (tostring v))) + (let [arglist [] + args (. args 1) + len (length args)] + (each [i v (ipairs args)] + (table.insert arglist + (match i + (1 ? (= len 1)) (.. "[" (tostring v) "]") + 1 (.. "[" (tostring v)) + len (.. (tostring v) "]") + _ (tostring v)))) arglist))) (fn multisym->sym [s] @@ -1,4 +1,7 @@ -(local core {}) +(local core {:_DESCRIPTION "Fennel-cljlib - functions from Clojure's core.clj implemented on top of Fennel." + :_VERSION "0.1.0" + :_COPYRIGHT "Copyright (C) 2020 Andrey Orst"}) + (local insert table.insert) (local unpack (or table.unpack _G.unpack)) @@ -144,9 +147,9 @@ arguments to `args`." (fn* core.seq "Create sequential table. Transforms original table to sequential table of key value pairs -stored as sequential tables in linear time. If `tbl` is an +stored as sequential tables in linear time. If `col` is an associative table, returns `[[key1 value1] ... [keyN valueN]]` table. -If `tbl` is sequential table, returns its shallow copy." +If `col` is sequential table, returns its shallow copy." [col] (let [res (empty [])] (match (type col) @@ -169,25 +172,25 @@ If `tbl` is sequential table, returns its shallow copy." (fn* core.first "Return first element of a table. Calls `seq` on its argument." - [tbl] - (when-some [tbl (seq tbl)] - (. tbl 1))) + [col] + (when-some [col (seq col)] + (. col 1))) (fn* core.rest "Returns table of all elements of a table but the first one. Calls `seq` on its argument." - [tbl] - (if-some [tbl (seq tbl)] - (vector (unpack tbl 2)) + [col] + (if-some [col (seq col)] + (vector (unpack col 2)) (empty []))) (fn* core.last "Returns the last element of a table. Calls `seq` on its argument." - [tbl] - (when-some [tbl (seq tbl)] - (var (i v) (next tbl)) + [col] + (when-some [col (seq col)] + (var (i v) (next col)) (while i - (local (_i _v) (next tbl i)) + (local (_i _v) (next col i)) (if _i (set v _v)) (set i _i)) v)) @@ -195,12 +198,11 @@ If `tbl` is sequential table, returns its shallow copy." (fn* core.butlast "Returns everything but the last element of a table as a new table. Calls `seq` on its argument." - [tbl] - (when-some [tbl (seq tbl)] - (table.remove tbl (length tbl)) - (when (not (empty? tbl)) - tbl))) - + [col] + (when-some [col (seq col)] + (table.remove col (length col)) + (when (not (empty? col)) + col))) (fn* core.conj "Insert `x` as a last element of indexed table `tbl`. Modifies `tbl`" @@ -243,10 +245,7 @@ If `tbl` is sequential table, returns its shallow copy." (apply concat (concat x y) xs))) (fn* core.reduce - "Reduce indexed table using function `f` and optional initial value `val`. - -([f table]) -([f val table]) + "Reduce collection `col` using function `f` and optional initial value `val`. `f` should be a function of 2 arguments. If val is not supplied, returns the result of applying f to the first 2 items in coll, then @@ -256,18 +255,18 @@ result of calling f with no arguments. If coll has only 1 item, it is returned and f is not called. If val is supplied, returns the result of applying f to val and the first item in coll, then applying f to that result and the 2nd item, etc. If coll contains no items, returns -val and f is not called." - ([f tbl] - (let [tbl (or (seq tbl) (empty []))] - (match (length tbl) +val and f is not called. Calls `seq` on `col`." + ([f col] + (let [col (or (seq col) (empty []))] + (match (length col) 0 (f) - 1 (. tbl 1) - 2 (f (. tbl 1) (. tbl 2)) - _ (let [[a b & rest] tbl] + 1 (. col 1) + 2 (f (. col 1) (. col 2)) + _ (let [[a b & rest] col] (reduce f (f a b) rest))))) - ([f val tbl] - (let [tbl (or (seq tbl) (empty []))] - (let [[x & xs] tbl] + ([f val col] + (let [col (or (seq col) (empty []))] + (let [[x & xs] col] (if (nil? x) val (reduce f (f val x) xs)))))) @@ -275,22 +274,20 @@ val and f is not called." (fn* core.reduce-kv "Reduces an associative table using function `f` and initial value `val`. -([f val table]) - `f` should be a function of 3 arguments. Returns the result of -applying `f` to `val`, the first key and the first value in coll, then -applying `f` to that result and the 2nd key and value, etc. If coll -contains no entries, returns `val` and `f` is not called. Note that -reduce-kv is supported on vectors, where the keys will be the -ordinals." - [f val tbl] +applying `f` to `val`, the first key and the first value in `coll`, +then applying `f` to that result and the 2nd key and value, etc. If +`coll` contains no entries, returns `val` and `f` is not called. Note +that reduce-kv is supported on sequential tables and strings, where +the keys will be the ordinals." + [f val col] (var res val) - (each [_ [k v] (pairs (or (seq tbl) (empty [])))] + (each [_ [k v] (pairs (or (seq col) (empty [])))] (set res (f res k v))) res) (fn* core.mapv - "Maps function `f` over one or more tables. + "Maps function `f` over one or more collections. Accepts arbitrary amount of tables, calls `seq` on each of it. Function `f` must take the same amount of parameters as the amount of @@ -298,76 +295,76 @@ tables passed to `mapv`. Applies `f` over first value of each table. Then applies `f` to second value of each table. Continues until any of the tables is exhausted. All remaining values are ignored. Returns a table of results." - ([f tbl] + ([f col] (local res (empty [])) - (each [_ v (ipairs (or (seq tbl) (empty [])))] + (each [_ v (ipairs (or (seq col) (empty [])))] (when-some [tmp (f v)] (insert res tmp))) res) - ([f t1 t2] + ([f col1 col2] (let [res (empty []) - t1 (or (seq t1) (empty [])) - t2 (or (seq t2) (empty []))] - (var (i1 v1) (next t1)) - (var (i2 v2) (next t2)) + col1 (or (seq col1) (empty [])) + col2 (or (seq col2) (empty []))] + (var (i1 v1) (next col1)) + (var (i2 v2) (next col2)) (while (and i1 i2) (when-some [tmp (f v1 v2)] (insert res tmp)) - (set (i1 v1) (next t1 i1)) - (set (i2 v2) (next t2 i2))) + (set (i1 v1) (next col1 i1)) + (set (i2 v2) (next col2 i2))) res)) - ([f t1 t2 t3] + ([f col1 col2 col3] (let [res (empty []) - t1 (or (seq t1) (empty [])) - t2 (or (seq t2) (empty [])) - t3 (or (seq t3) (empty []))] - (var (i1 v1) (next t1)) - (var (i2 v2) (next t2)) - (var (i3 v3) (next t3)) + col1 (or (seq col1) (empty [])) + col2 (or (seq col2) (empty [])) + col3 (or (seq col3) (empty []))] + (var (i1 v1) (next col1)) + (var (i2 v2) (next col2)) + (var (i3 v3) (next col3)) (while (and i1 i2 i3) (when-some [tmp (f v1 v2 v3)] (insert res tmp)) - (set (i1 v1) (next t1 i1)) - (set (i2 v2) (next t2 i2)) - (set (i3 v3) (next t3 i3))) + (set (i1 v1) (next col1 i1)) + (set (i2 v2) (next col2 i2)) + (set (i3 v3) (next col3 i3))) res)) - ([f t1 t2 t3 & tbls] - (let [step (fn step [tbls] - (if (->> tbls + ([f col1 col2 col3 & cols] + (let [step (fn step [cols] + (if (->> cols (mapv #(not= (next $) nil)) (reduce #(and $1 $2))) - (cons (mapv #(. (or (seq $) (empty [])) 1) tbls) (step (mapv #(do [(unpack $ 2)]) tbls))) + (cons (mapv #(. (or (seq $) (empty [])) 1) cols) (step (mapv #(do [(unpack $ 2)]) cols))) (empty []))) res (empty [])] - (each [_ v (ipairs (step (consj tbls t3 t2 t1)))] + (each [_ v (ipairs (step (consj cols col3 col2 col1)))] (when-some [tmp (apply f v)] (insert res tmp))) res))) -(fn* core.filter [pred tbl] - (if-let [tbl (seq tbl)] - (let [f (. tbl 1) - r [(unpack tbl 2)]] +(fn* core.filter + "Returns a sequential table of the items in `col` for which `pred` + returns logical true." + [pred col] + (if-let [col (seq col)] + (let [f (. col 1) + r [(unpack col 2)]] (if (pred f) (cons f (filter pred r)) (filter pred r))) (empty []))) -(fn kvseq [tbl] +(fn* core.kvseq "Transforms any table kind to key-value sequence." + [tbl] (let [res (empty [])] (each [k v (pairs tbl)] (insert res [k v])) res)) - - -(fn* core.identity - "Returns its argument." - [x] - x) +(fn* core.identity "Returns its argument." [x] x) (fn* core.comp + "Compose functions." ([] identity) ([f] f) ([f g] @@ -481,6 +478,7 @@ found in the table." res)) (fn* core.remove-method + "Remove method from `multifn` for given `dispatch-val`." [multifn dispatch-val] (tset (. (getmetatable multifn) :multimethods) dispatch-val nil) multifn) @@ -615,12 +613,13 @@ that would apply to that value, or `nil` if none apply and no default." ([x y & xs] (reduce #(and $1 $2) (eq x y) (mapv #(eq x $) xs)))) -(fn* core.memoize [f] +(fn* core.memoize "Returns a memoized version of a referentially transparent function. The memoized version of the function keeps a cache of the mapping from arguments to results and, when calls with the same arguments are repeated often, has higher performance at the expense of higher memory use." + [f] (let [memo (setmetatable {} {:__index (fn [tbl key] (each [k v (pairs tbl)] diff --git a/doc/0.1.0/cljlib-macros.md b/doc/0.1.0/cljlib-macros.md new file mode 100644 index 0000000..d99dad6 --- /dev/null +++ b/doc/0.1.0/cljlib-macros.md @@ -0,0 +1,288 @@ +# Cljlib-macros.fnl +Macro module for Fennel Cljlib. + +## Metadata macros +Metadata in Fennel is a pretty tough subject, as there's no such thing as metadata in Lua. +Therefore, the metadata usage in Fennel is more limited compared to Clojure. +This library provides some facilities for metadata management, which are experimental and should be used with care. + +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`. + + +### `when-meta` +This macros is a 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`. + + +### `with-meta` +Attach metadata to a value. + + >> (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 + +When metadata feature is not enabled, returns the value without additional metadata. + + +### `meta` +Get metadata table from object: + + >> (meta (with-meta {} {:meta "data"})) + { + :meta "data" + } + + +## `def` and `defonce` +`def` is wrappers around `local` which can declare variables inside namespace, and as local at the same time: + + >> (def ns {}) + >> (def a 10) + >> a + 10 + >> (def ns.a 20) + >> a + 20 + >> ns.a + 20 + +Both `ns.a` and `a` refer to the same value. + +`defonce` ensures that the binding isn't overridden by another `defonce`: + + >> (defonce ns {}) + >> (defonce ns.a 42) + >> (defonce ns 10) + >> ns + {:a 42} + >> a + 42 + +Both `def` and `defonce` support literal metadata table as first argument, or a :dynamic keyword, that uses Fennel `var` instead of `local`: + + >> (def {:dynamic true} a 10) + >> (set a 20) + >> a + 20 + >> (defonce :dynamic b 40) + >> (set b 42) + >> b + 42 + +Documentation string can be attached to value via `:doc` keyword. +However it is not recommended to attach metadata to everything except tables and functions: + + ;; Bad, may overlap with existing documentation for 299792458, if any + >> (def {:doc "The speed of light in m/s"} c 299792458) + >> (doc c) + c + The speed of light in m/s + + ;; OK + >> (def {:doc "default connection options"} + defaults {:port 1234 + :host localhost}) + + +## `fn*` +Clojure's `fn` equivalent. +Returns a function of fixed amount of arguments by doing runtime dispatch based on argument count. +Capable of producing multi-arity functions: + + (fn* square "square number" [x] (^ x 2)) + + (square 9) ;; => 81.0 + (square 1 2) ;; => error + + (fn* range + "Returns increasing sequence of numbers from `lower' to `upper'. + If `lower' is not provided, sequence starts from zero. + Accepts optional `step'" + ([upper] (range 0 upper 1)) + ([lower upper] (range lower upper 1)) + ([lower upper step] + (let [res []] + (for [i lower (- upper step) step] + (table.insert res i)) + res))) + + (range 10) ;; => [0 1 2 3 4 5 6 7 8 9] + (range -10 0) ;; => [-10 -9 -8 -7 -6 -5 -4 -3 -2 -1] + (range 0 1 0.2) ;; => [0.0 0.2 0.4 0.6 0.8] + +Both variants support up to one arity with `& more`: + + (fn* vec [& xs] xs) + + (vec 1 2 3) ;; => [1 2 3] + + (fn* add + "sum two or more values" + ([] 0) + ([a] a) + ([a b] (+ a b)) + ([a b & more] (add (+ a b) (unpack more)))) + + (add) ;; => 0 + (add 1) ;; => 1 + (add 1 2) ;; => 3 + (add 1 2 3 4) ;; => 10 + +One extra capability of `fn*` supports the same semantic as `def` regarding namespaces: + + (local ns {}) + + (fn* ns.plus + ([] 0) + ([x] x) + ([x y] (+ x y)) + ([x y & zs] (apply plus (+ x y) zs))) + + ns + +Note, that `plus` is used without `ns` part, e.g. not `ns.plus`. +If we `require` this code from file in the REPL, we will see that our `ns` has single function `plus`: + + >> (local ns (require :module)) + >> ns + {add #<function 0xbada55code>} + +This is possible because `fn*` separates the namespace part from the function name, and creates a `local` variable with the same name as function, then defines the function within lexical scope of `do`, sets `namespace.foo` to it and returns the function object to the outer scope. + + (local plus + (do (fn plus [...] + ;; plus body + ) + (set ns.plus plus) + plus)) + +See `core.fnl` for more examples. + + +## `fn+` +Works similarly to Fennel's `fn`, by creating ordinary function without arity semantics, except does the namespace automation like `fn*`, and has the same order of arguments as the latter: + + (local ns {}) + + ;; module & file-local functions + (fn+ ns.double + "double the number" + [x] + (* x 2)) + + (fn+ ns.triple + [x] + (* x 3)) + + ;; no namespace, file-local function + (fn+ quadruple + [x] + (* x 4)) + + ;; anonymous file-local function + (fn+ [x] (* x 5)) + + ns + +See `core.fnl` for more examples. + + +## `if-let` and `when-let` +When test expression is not `nil` or `false`, evaluates the first body form with the `name` bound to the result of the expressions. + + (if-let [val (test)] + (print val) + :fail) + +Expanded form: + + (let [tmp (test)] + (if tmp + (let [val tmp] + (print val)) + :fail)) + +`when-let` is mostly the same, except doesn't have false branch and accepts any amount of forms: + + (when-let [val (test)] + (print val) + val) + +Expanded form: + + (let [tmp (test)] + (if tmp + (let [val tmp] + (print val) + val))) + + +## `if-some` and `when-some` +Much like `if-let` and `when-let`, except tests expression for not being `nil`. + + (when-some [val (foo)] + (print (.. "val is not nil: " val)) + val) + + +## `into` +Clojure's `into` function is implemented as macro, because Fennel has no runtime distinction between `[]` and `{}` tables, since Lua also doesn't feature this feature. +However we can do this at compile time. + + (into [1 2 3] [4 5 6]) ;; => [1 2 3 4 5 6] + (into [] {:a 1 :b 2 :c 3 :d 4}) ;; => [["d" 4] ["a" 1] ["b" 2] ["c" 3]] + (into {} [[:d 4] [:a 1] [:b 2] [:c 3]]) ;; => {:a 1 :b 2 :c 3 :d 4} + (into {:a 0 :e 5} {:a 1 :b 2 :c 3 :d 4}) ;; => {:a 1 :b 2 :c 3 :d 4 :e 5} + +Because the type check at compile time it will only respect the type when literal representation is used. +If a variable holding the table, its type is checked at runtime. +Empty tables default to sequential ones: + + (local a []) + (into a {:a 1 :b 2}) ;; => [["b" 2] ["a" 1]] + + (local b {}) + (into b {:a 1 :b 2}) ;; => [["b" 2] ["a" 1]] + +However, if target table is not empty, its type can be deduced: + + (local a {:c 3}) + (into a {:a 1 :b 2}) ;; => {:a 1 :b 2 :c 3} + + (local b [1]) + (into b {:a 1 :b 2}) ;; => [1 ["b" 2] ["a" 1]] + +Note that when converting associative table into sequential table order is determined by the `pairs` function. +Also note that if variable stores the table has both integer key 1, and other associative keys, the type will be the same as of sequential table. + + +## `defmulti` and `defmethod` +A bit more simple implementations of Clojure's `defmulti` and `defmethod`. +`defmulti` macros returns an empty table with `__call` metamethod, that calls dispatching function on its arguments. +Methods are defined inside `multimethods` table, which is also stored in the metatable. + +`defmethod` adds a new method to the metatable of given `multifn`. +It accepts the multi-fn table as its first argument, the dispatch value as second, and Fennel's arglist followed by the body: + + (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. diff --git a/doc/0.1.0/cljlib.md b/doc/0.1.0/cljlib.md new file mode 100644 index 0000000..1226591 --- /dev/null +++ b/doc/0.1.0/cljlib.md @@ -0,0 +1,674 @@ +# Cljlib.fnl +Fennel-cljlib - functions from Clojure's core.clj implemented on top of Fennel. + +Documentation for version: 0.1.0 + +## `add` +Function signature: + +``` +(add + ([a]) + ([a b]) + ([a b c]) + ([a b c d]) + ([a b c d & rest])) +``` + +Sum arbitrary amount of numbers. + +## `apply` +Function signature: + +``` +(apply + ([f args]) + ([f a args]) + ([f a b args]) + ([f a b c args]) + ([f a b c d & args])) +``` + +Apply `f` to the argument list formed by prepending intervening +arguments to `args`. + +## `assoc` +Function signature: + +``` +(assoc + ([tbl k v]) + ([tbl k v & kvs])) +``` + +Associate key `k` with value `v` in `tbl`. + +## `boolean?` +Function signature: + +``` +(boolean? [x]) +``` + +Test if `x` is a Boolean + +## `butlast` +Function signature: + +``` +(butlast [col]) +``` + +Returns everything but the last element of a table as a new + table. Calls `seq` on its argument. + +## `comp` +Function signature: + +``` +(comp + ([f]) + ([f g]) + ([f g & fs])) +``` + +Compose functions. + +## `complement` +Function signature: + +``` +(complement [f]) +``` + +Takes a function `f` and returns the function that takes the same +amount of arguments as `f`, has the same effect, and returns the +oppisite truth value. + +## `concat` +Function signature: + +``` +(concat + ([x]) + ([x y]) + ([x y & xs])) +``` + +Concatenate tables. + +## `conj` +Function signature: + +``` +(conj + ([tbl]) + ([tbl x]) + ([tbl x & xs])) +``` + +Insert `x` as a last element of indexed table `tbl`. Modifies `tbl` + +## `cons` +Function signature: + +``` +(cons [x tbl]) +``` + +Insert `x` to `tbl` at the front. Modifies `tbl`. + +## `constantly` +Function signature: + +``` +(constantly [x]) +``` + +Returns a function that takes any number of arguments and returns `x`. + +## `dec` +Function signature: + +``` +(dec [x]) +``` + +Decrease number by one + +## `div` +Function signature: + +``` +(div + ([a]) + ([a b]) + ([a b c]) + ([a b c d]) + ([a b c d & rest])) +``` + +Divide arbitrary amount of numbers. + +## `double?` +Function signature: + +``` +(double? [x]) +``` + +Test if `x` is a number with floating point data. + +## `empty?` +Function signature: + +``` +(empty? [x]) +``` + +Check if collection is empty. + +## `eq` +Function signature: + +``` +(eq + ([x]) + ([x y]) + ([x y & xs])) +``` + +Deep compare values. + +## `even?` +Function signature: + +``` +(even? [x]) +``` + +Test if value is even. + +## `every?` +Function signature: + +``` +(every? [pred tbl]) +``` + +Test if every item in `tbl` satisfies the `pred`. + +## `false?` +Function signature: + +``` +(false? [x]) +``` + +Test if `x` is `false` + +## `filter` +Function signature: + +``` +(filter [pred col]) +``` + +Returns a sequential table of the items in `col` for which `pred` + returns logical true. + +## `first` +Function signature: + +``` +(first [col]) +``` + +Return first element of a table. Calls `seq` on its argument. + +## `ge` +Function signature: + +``` +(ge + ([x]) + ([x y]) + ([x y & more])) +``` + +Returns true if nums are in monotonically non-increasing order + +## `get` +Function signature: + +``` +(get + ([tbl key]) + ([tbl key not-found])) +``` + +Get value from the table by accessing it with a `key`. +Accepts additional `not-found` as a marker to return if value wasn't +found in the table. + +## `get-in` +Function signature: + +``` +(get-in + ([tbl keys]) + ([tbl keys not-found])) +``` + +Get value from nested set of tables by providing key sequence. +Accepts additional `not-found` as a marker to return if value wasn't +found in the table. + +## `get-method` +Function signature: + +``` +(get-method [multifn dispatch-val]) +``` + +Given a multimethod and a dispatch value, returns the dispatch `fn` +that would apply to that value, or `nil` if none apply and no default. + +## `gt` +Function signature: + +``` +(gt + ([x]) + ([x y]) + ([x y & more])) +``` + +Returns true if nums are in monotonically increasing order + +## `hash-map` +Function signature: + +``` +(hash-map + ([& kvs])) +``` + +Create associative table from keys and values + +## `identity` +Function signature: + +``` +(identity [x]) +``` + +Returns its argument. + +## `inc` +Function signature: + +``` +(inc [x]) +``` + +Increase number by one + +## `int?` +Function signature: + +``` +(int? [x]) +``` + +Test if `x` is a number without floating point data. + +## `kvseq` +Function signature: + +``` +(kvseq [tbl]) +``` + +Transforms any table kind to key-value sequence. + +## `last` +Function signature: + +``` +(last [col]) +``` + +Returns the last element of a table. Calls `seq` on its argument. + +## `le` +Function signature: + +``` +(le + ([x]) + ([x y]) + ([x y & more])) +``` + +Returns true if nums are in monotonically non-decreasing order + +## `lt` +Function signature: + +``` +(lt + ([x]) + ([x y]) + ([x y & more])) +``` + +Returns true if nums are in monotonically decreasing order + +## `map?` +Function signature: + +``` +(map? [tbl]) +``` + +Check whether `tbl` is an associative table. + +## `mapv` +Function signature: + +``` +(mapv + ([f col]) + ([f col1 col2]) + ([f col1 col2 col3]) + ([f col1 col2 col3 & cols])) +``` + +Maps function `f` over one or more collections. + +Accepts arbitrary amount of tables, calls `seq` on each of it. +Function `f` must take the same amount of parameters as the amount of +tables passed to `mapv`. Applies `f` over first value of each +table. Then applies `f` to second value of each table. Continues until +any of the tables is exhausted. All remaining values are +ignored. Returns a table of results. + +## `memoize` +Function signature: + +``` +(memoize [f]) +``` + +Returns a memoized version of a referentially transparent function. +The memoized version of the function keeps a cache of the mapping from +arguments to results and, when calls with the same arguments are +repeated often, has higher performance at the expense of higher memory +use. + +## `methods` +Function signature: + +``` +(methods [multifn]) +``` + +Given a multimethod, returns a map of dispatch values -> dispatch fns + +## `mul` +Function signature: + +``` +(mul + ([a]) + ([a b]) + ([a b c]) + ([a b c d]) + ([a b c d & rest])) +``` + +Multiply arbitrary amount of numbers. + +## `neg-int?` +Function signature: + +``` +(neg-int? [x]) +``` + +Test if `x` is a negetive integer. + +## `neg?` +Function signature: + +``` +(neg? [x]) +``` + +Test if `x` is less than zero. + +## `nil?` +Function signature: + +``` +(nil? + ([x])) +``` + +Test if value is nil. + +## `not-any?` +Function signature: + +``` +(not-any? pred tbl) +``` + +Test if no item in `tbl` satisfy the `pred`. + +## `not-empty` +Function signature: + +``` +(not-empty [x]) +``` + +If `x` is empty, returns `nil`, otherwise `x`. + +## `odd?` +Function signature: + +``` +(odd? [x]) +``` + +Test if value is odd. + +## `pos-int?` +Function signature: + +``` +(pos-int? [x]) +``` + +Test if `x` is a positive integer. + +## `pos?` +Function signature: + +``` +(pos? [x]) +``` + +Test if `x` is greater than zero. + +## `range` +Function signature: + +``` +(range + ([upper]) + ([lower upper]) + ([lower upper step])) +``` + +return range of of numbers from `lower` to `upper` with optional `step`. + +## `reduce` +Function signature: + +``` +(reduce + ([f col]) + ([f val col])) +``` + +Reduce collection `col` using function `f` and optional initial value `val`. + +`f` should be a function of 2 arguments. If val is not supplied, +returns the result of applying f to the first 2 items in coll, then +applying f to that result and the 3rd item, etc. If coll contains no +items, f must accept no arguments as well, and reduce returns the +result of calling f with no arguments. If coll has only 1 item, it is +returned and f is not called. If val is supplied, returns the result +of applying f to val and the first item in coll, then applying f to +that result and the 2nd item, etc. If coll contains no items, returns +val and f is not called. Calls `seq` on `col`. + +## `reduce-kv` +Function signature: + +``` +(reduce-kv [f val col]) +``` + +Reduces an associative table using function `f` and initial value `val`. + +`f` should be a function of 3 arguments. Returns the result of +applying `f` to `val`, the first key and the first value in `coll`, +then applying `f` to that result and the 2nd key and value, etc. If +`coll` contains no entries, returns `val` and `f` is not called. Note +that reduce-kv is supported on sequential tables and strings, where +the keys will be the ordinals. + +## `remove-all-methods` +Function signature: + +``` +(remove-all-methods [multifn]) +``` + +Removes all of the methods of multimethod + +## `remove-method` +Function signature: + +``` +(remove-method [multifn dispatch-val]) +``` + +Remove method from `multifn` for given `dispatch-val`. + +## `rest` +Function signature: + +``` +(rest [seq]) +``` + +Returns table of all elements of a table but the first one. Calls + `seq` on its argument. + +## `reverse` +Function signature: + +``` +(reverse [tbl]) +``` + +Returns table with same items as in `tbl` but in reverse order. + +## `seq` +Function signature: + +``` +(seq [col]) +``` + +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 `[[key1 value1] ... [keyN valueN]]` table. +If `col` is sequential table, returns its shallow copy. + +## `seq?` +Function signature: + +``` +(seq? [tbl]) +``` + +Check whether `tbl` is an sequential table. + +## `some` +Function signature: + +``` +(some [pred tbl]) +``` + +Test if any item in `tbl` satisfies the `pred`. + +## `string?` +Function signature: + +``` +(string? [x]) +``` + +Test if `x` is a string. + +## `sub` +Function signature: + +``` +(sub + ([a]) + ([a b]) + ([a b c]) + ([a b c d]) + ([a b c d & rest])) +``` + +Subtract arbitrary amount of numbers. + +## `true?` +Function signature: + +``` +(true? [x]) +``` + +Test if `x` is `true` + +## `vector` +Function signature: + +``` +(vector [& args]) +``` + +Constructs sequential table out of it's arguments. + +## `zero?` +Function signature: + +``` +(zero? [x]) +``` + +Test if value is zero. + + +<!-- Generated with Fenneldoc 0.0.1-->
\ No newline at end of file diff --git a/tests/fn.fnl b/tests/fn.fnl index e0af7cb..aa86b12 100644 --- a/tests/fn.fnl +++ b/tests/fn.fnl @@ -7,21 +7,21 @@ "docstring" [x] x) (assert-eq (meta f) (when-meta {:fnl/docstring "docstring" - :fnl/arglist ["x"]})) + :fnl/arglist ["[x]"]})) (fn* f "docstring" ([x] x)) (assert-eq (meta f) (when-meta {:fnl/docstring "docstring" - :fnl/arglist ["x"]})) + :fnl/arglist ["([x])"]})) (fn* f "docstring" ([x] x) ([x y] (+ x y))) (assert-eq (meta f) (when-meta {:fnl/docstring "docstring" - :fnl/arglist ["\n [x]" - "\n [x y]"]})) + :fnl/arglist ["\n ([x])" + "\n ([x y])"]})) (fn* f "docstring" @@ -29,9 +29,9 @@ ([x y] (+ x y)) ([x y & z] (+ x y))) (assert-eq (meta f) (when-meta {:fnl/docstring "docstring" - :fnl/arglist ["\n [x]" - "\n [x y]" - "\n [x y & z]"]})))) + :fnl/arglist ["\n ([x])" + "\n ([x y])" + "\n ([x y & z])"]})))) (deftest fn+ (testing "fn+ meta" |