summaryrefslogtreecommitdiff
path: root/README.org
blob: c17a02ebbf5be15569f3945a6d5366eb0a4e29b2 (plain) (blame)
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

* Fennel Cljlib
Library for [[https://fennel-lang.org/][Fennel]] language that adds a lot of functions from [[https://clojure.org/][Clojure]] standard library.
This is not a one to one port of Clojure =core=, because many Clojure functions require certain guarantees, like immutability of the underlying data structures.
Therefore some names were changed, but they should be still recognizable.

Goals of this project are:

- Have a self contained library, with no dependencies, that provides a set of useful functions from Clojure =core=,
- 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
*** =fn*=
Clojure's =fn= equivalent.
Returns a function of fixed arity by doing runtime dispatch, based on argument amount.
Capable of producing multi-arity functions:

#+begin_src fennel
  (fn* square "square number" [x] (^ x 2))

  (square 9) ;; 81
  (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 1) 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* list [& xs] xs)

  (list 1 2 3)
  ;; [1 2 3]
#+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 =nil=.

#+begin_src fennel
  (when-some [val (foo)]
    (print (.. "val is not nil: " val))
    val)
#+end_src

*** =into=
Clojure's =into= function 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})
  ;; [["a" 1] ["b" 2] ["c" 3] ["d" 4]]

  (into {} [[:a 1] [:b 2] [:c 3] [:d 4]])
  ;; {: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
** 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 always returns sequential tables:

#+begin_src fennel
  (seq [1 2 3 4 5])
  ;; [1 2 3 4 5]

  (seq {:a 1 :b 2 :c 3 :d 4})
  ;; [["a" 1] ["b" 2] ["c" 3] ["d" 4]]
#+end_src

See [[*=into=][=into=]] on how to transform such sequence back into associative table.

*** =first= and =rest=
=first= returns first value of a table.
It call =seq= on it, so this takes linear time for any 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= works the same way, but returns everything except first argument as a table.
It also calls =seq= on it's argument.

#+begin_src fennel
  (rest [1 2 3])
  ;; [2 3]

  (rest {:host "localhost" :port 2344 :options {}})
  ;; [["port" 2344] ["options" {}]]
#+end_src

*** =conj= and =cons=
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.
Both functions return the resulting table, so it is possible to nest these, or build a classic =map=:

#+begin_src fennel
  (fn map [f col]
    (if-some [val (first col)]
      (cons (f val) (map f (rest col)))
      []))
#+end_src

=cons= accepts value as its first argument and table as second and puts value to the front of the table.
=col= is not modified by the =map= function described above, but the =[]= table in the =else= branch of =is-some= is.

=conj= accepts table as it's 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

*** =mapv= and =mapkv=
Mapping functions.
In Clojure we have a =seq= abstraction, that allows us to use single =mapv= on both vectors, and hash tables.
However in Fennel, and Lua there's no efficient way of checking if we got an associative or indexed table.
For this reason, there are two functions - =mapv=, or which maps over vectors, and =mapkv= which maps over associative tables (=kv= is for key-value).
Here, =mapv= works the same as =mapv= from Clojure, except it doesn't yield a transducer (yet?) when only function is supplied.
=mapkv= also works similarly, except it requires for function you pass to accept twice the amount of tables you pass to =mapkv=.

#+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"]

  (mapkv (fn [k v] [k v]) {:host "localhost" :port 1344})
  ;; [["port" 1344] ["host" "localhost"]]
#+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

#  LocalWords:  Luajit VM