1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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.
|