Skip to content

Commit 7fd5f98

Browse files
authored
Haskell-inspired monadic do notation (#222)
* Introduce haskell-inspired do-let macro * fix date
1 parent 657f4a7 commit 7fd5f98

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog #
22

3+
## Version 2.3.2
4+
5+
Date: 2018-11-07
6+
7+
- Introduce haskell-inspired do-let macro
8+
39
## Version 2.3.1 ##
410

511
Date: 2018-10-31

project.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(defproject funcool/cats "2.3.1"
1+
(defproject funcool/cats "2.3.2"
22
:description "Category Theory abstractions for Clojure"
33
:url "https://github.com/funcool/cats"
44
:license {:name "BSD (2 Clause)"

src/cats/core.cljc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,3 +1013,39 @@
10131013
([ctx f tv]
10141014
(ctx/with-context ctx
10151015
(p/-traverse (p/-get-context tv) f tv))))
1016+
1017+
(defmacro do-let
1018+
"Haskell-inspired monadic do notation
1019+
it allows one to drop the _ when we don't need the extracted value
1020+
1021+
Basically,
1022+
(do-let
1023+
a
1024+
b
1025+
[c d
1026+
e f]
1027+
x
1028+
y)
1029+
1030+
Translates into:
1031+
1032+
(mlet
1033+
[_ a
1034+
_ b
1035+
c d
1036+
e f
1037+
_ x]
1038+
y)
1039+
"
1040+
[& forms]
1041+
(assert (not (empty? forms)) "do-let must have at least one argument")
1042+
(assert (not (vector? (last forms))) "Last argument of do-let must not be a vector")
1043+
(if (= 1 (count forms))
1044+
`(do (assert (not (satisfies? p/Monad ~(first forms))) "Single argument of do-let must implement Monad protocol")
1045+
~(first forms))
1046+
`(mlet ~(vec (reduce (fn [acc form]
1047+
(cond (vector? form) (into acc form)
1048+
:else (into acc ['_ form])))
1049+
[]
1050+
(butlast forms)))
1051+
~(last forms))))

test/cats/core_spec.cljc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,36 @@
298298
(t/is (= (maybe/just 1)
299299
(ctx/with-context maybe/context
300300
(m/foldm m-div 1 [])))))))
301+
302+
(t/deftest do-let-tests
303+
(t/testing "Support regular let bindings inside do-let"
304+
(t/is (= (maybe/just 2)
305+
(m/do-let [i (maybe/just 1)
306+
:let [i (inc i)]]
307+
(m/return i)))))
308+
309+
(t/testing "Support :when guards inside its bindings"
310+
(t/is (= (maybe/nothing)
311+
(m/do-let [i (maybe/just 2)
312+
:when (> i 2)]
313+
(m/return i))))
314+
(t/is (= [3 4 5]
315+
(m/do-let [i [1 2 3 4 5]
316+
:when (> i 2)]
317+
(m/return i)))))
318+
319+
(t/testing "Support one single form"
320+
(t/is (= (maybe/just 2)
321+
(m/do-let (maybe/just 2)))))
322+
323+
(t/testing "Support multiple single form"
324+
(t/is (= (maybe/just 3)
325+
(m/do-let (maybe/just 2)
326+
(maybe/just 3)))))
327+
328+
(t/testing "Bound variables are always in scope"
329+
(t/is (= (maybe/just 6)
330+
(m/do-let [x (maybe/just 2)]
331+
(maybe/just x)
332+
[y (maybe/just (+ 2 x))]
333+
(maybe/just (+ 2 y)))))))

0 commit comments

Comments
 (0)