Skip to content

Commit

Permalink
Remove some macros and add into combinator
Browse files Browse the repository at this point in the history
  • Loading branch information
anler committed Nov 14, 2017
1 parent df9d35a commit f568b43
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 105 deletions.
117 changes: 62 additions & 55 deletions src/clj/try/core.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
(ns try.core
(:refer-clojure :exclude [val sequence]
:rename {apply clj-apply
map clj-map})
map clj-map
into clj-into})
(:require [clojure.pprint]))


Expand All @@ -25,6 +26,14 @@
[try]
(:value try))

(defmacro val-or
"Retrieve the value of try if it's a success or evaluate and return
default if it's a failure."
[try default]
`(if (success? ~try)
(val ~try)
~default))

(defn val-throw
"Retrieve the value of try, but throw an exception if it's a failed
Try. This is the same as dereferencing try with deref or @."
Expand Down Expand Up @@ -56,7 +65,7 @@
[value]
(->Try ::failure value))

(defmacro tryc
(defmacro try>
"Wrap body in a try/catch that returns a successful Try
if no exception is raised, or a failed Try if an exception
is raised."
Expand All @@ -67,10 +76,10 @@
(catch Exception e#
(fail e#))))

(defmacro tryc-let
(defmacro try-let
"Wrap a let with bindings and body in a tryc."
[bindings & body]
`(tryc
`(try>
(let ~bindings
~@body)))

Expand All @@ -81,43 +90,13 @@
(succeed (f (val try)))
try))

(defmacro map->
"Wrap each form in a map application and thread try through it.
Equivalent to:
(-> try
(map first-form)
(map second-form)
...)"
[try & forms]
(let [forms* (for [form forms]
`(map ~(if (seq? form)
`#(~(first form) % ~@(next form))
`#(~form %))))]
`(->> ~try
~@forms*)))

(defn map-failure
"Apply function f to try's value if is a failed Try."
[f try]
(if (success? try)
try
(fail (f (val try)))))

(defmacro map-failure->
"Wrap each form in a map-failure application and thread try through it.
Equivalent to:
(-> try
(map-failure first-form)
(map-failure second-form)
...)"
[try & forms]
(let [forms* (for [form forms]
`(map-failure ~(if (seq? form)
`#(~(first form) % ~@(next form))
`#(~form %))))]
`(->> ~try
~@forms*)))

(defn bimap
"Apply function f to try's value if it's a success, otherwise apply
function g to try's value if it's a failure."
Expand All @@ -127,7 +106,14 @@
(defn apply
"Apply value in ftry if it's a success to the values in all the
try if all of them are also a success. If ftry is a failure it is
returned, otherwise the first failed try is returned."
returned, otherwise the first failed try is returned.
Examples:
(apply (succeed +) (succeed 1) (succeed 2) (succeed 3))
=> #try.core.Try{:tag :try.core/success, :value 6}
(apply (succeed +) (succeed 1) (fail 2) (fail 3))
=> #try.core.Try{:tag :try.core/failure, :value 2}"
[ftry & try]
(if (success? ftry)
(if (every? success? try)
Expand All @@ -137,30 +123,51 @@

(defn bind
"Apply f to try's value if is successful and return its result,
otherwise return try."
[try f]
(if (success? try)
(f (val try))
try))

(defmacro bind->
"Wrap each form in a bind application and thread try through it.
Equivalent to:
(-> try
(bind first-form)
(bind second-form)
...)"
[try & forms]
(let [forms* (for [form forms]
`(bind ~(if (seq? form)
`#(~(first form) % ~@(next form))
`#(~form %))))]
`(-> ~try
~@forms*)))
otherwise return try.
Examples:
(bind (succeed 1)
#(succeed (+ 2 %)))
=> #try.core.Try{:tag :try.core/success, :value 3}
(bind (fail 1)
#(succeed (+ 2 %)))
=> #try.core.Try{:tag :try.core/failure, :value 1}
(bind (succeed 1)
#(fail (str \"err\" %))
#(succeed (+ 1 %)))
=> #try.core.Try{:tag :try.core/failure, :value \"err1\"}"
[try & fs]
(let [x (volatile! try)]
(loop [[f & fs] fs]
(when (and f (success? @x))
(vreset! x (f @@x))
(recur fs)))
@x))

(defn sequence
"Transform a collection coll of try items in a try where the value is
a collection of each try value. If given an empty collection,
returns a successful try with the empty vector as value."
[coll]
(clj-apply apply (succeed (partial conj [])) coll))

(defn into
"If all items in coll are successful Try, return a successful Try
which value is the to collection filled with the value of each Try
in coll. Otherwise return a failed Try which value is the to
collection filled with the value of each Try in coll.
Examples:
(into [] (list (succeed 1) (succeed 2) (succeed 3)))
=> #try.core.Try{:tag :try.core/success, :value [1 2 3]}
(into [] [(succeed 1) (fail 2) (fail 3)])
=> #try.core.Try{:tag :try.core/failure, :value [2 3]}
"
[to coll]
(if (empty? coll)
(succeed to)
(let [[successes failures] (partition-by success? coll)]
(if (empty? failures)
(succeed (clj-into to (clj-map val successes)))
(fail (clj-into to (clj-map val failures)))))))
95 changes: 45 additions & 50 deletions test/clj/try/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@
(t/is (= :some-val
(try/val (try/succeed :some-val))))))

(t/deftest val-or-test
(t/testing "val-or returns the value of a successful Try."
(t/is (= :some-val
(try/val-or (try/succeed :some-val) :default-val))))
(t/testing "val-or doesn't evaluate the default value if given a successful Try."
(let [effect (volatile! nil)
result (try/val-or (try/succeed :some-val) (do (reset! effect :executed)))]
(t/is (= :some-val
result))
(t/is (nil? @effect))))
(t/testing "val returns the default value if given a failed Try."
(t/is (= :default-val
(try/val-or (try/fail :some-val) :default-val)))))

(t/deftest val-throw-test
(t/testing "val-throw returns the value of a successful Try."
(t/is (= :some-val
Expand All @@ -55,21 +69,21 @@
(t/is (= "called"
@(try/succeed :some-val))))))

(t/deftest tryc-test
(t/testing "tryc returns a successful Try if no exception is thrown."
(t/is (try/success? (try/tryc :some-val))))
(t/deftest try>-test
(t/testing "try> returns a successful Try if no exception is thrown."
(t/is (try/success? (try/try> :some-val))))
(t/testing "tryc returns a failed Try if an exception is thrown."
(t/is (try/failure? (try/tryc (throw (Exception. "err")))))))
(t/is (try/failure? (try/try> (throw (Exception. "err")))))))

(t/deftest tryc-let-test
(t/testing "tryc-let is like tryc but accepts bindings"
(t/testing "try-let is like tryc but accepts bindings"
(t/is (= :some-val
@(try/tryc-let [v :some-val]
v))))
(t/testing "tryc-let catches any exception thrown in the let."
@(try/try-let [v :some-val]
v))))
(t/testing "try-let catches any exception thrown in the let."
(t/is (try/failure?
(try/tryc-let [v (throw (Exception. "err"))]
v)))))
(try/try-let [v (throw (Exception. "err"))]
v)))))

(t/deftest map-test
(t/testing "map applies f to a successful Try value."
Expand All @@ -83,19 +97,6 @@
(try/map #(throw (IllegalArgumentException. (str %)))
(try/succeed 3))))))

(t/deftest map->-test
(t/testing "map-> applies multiple functions to a successful Try value."
(t/is (= 9
(try/val (try/map-> (try/succeed 3)
inc
inc
(+ 4)))))
(t/is (= 3
(try/val (try/map-> (try/fail 3)
inc
inc
(+ 4)))))))

(t/deftest map-failure-test
(t/testing "map-failure applies f to a failed Try value."
(t/is (= 3
Expand All @@ -108,19 +109,6 @@
(try/map-failure #(throw (IllegalArgumentException. (str %)))
(try/fail 3))))))

(t/deftest map-failure->-test
(t/testing "map-failure-> applies multiple functions to a failed Try value."
(t/is (= 9
(try/val (try/map-failure-> (try/fail 3)
inc
inc
(+ 4)))))
(t/is (= 3
(try/val (try/map-failure-> (try/succeed 3)
inc
inc
(+ 4)))))))

(t/deftest bimap-test
(t/testing "bimap applies f to a successful Try value."
(t/is (= 4
Expand Down Expand Up @@ -167,26 +155,17 @@
(try/fail 3)))))))

(t/deftest bind-test
(t/testing "bind applies f to a successful Try."
(t/is (= 4
(t/testing "bind applies each f while they return a successful Try."
(t/is (= 5
(try/val (try/bind (try/succeed 3)
(comp try/succeed inc)
(comp try/succeed inc))))))
(t/testing "bind doesn't apply f to a failed Try."
(t/testing "bind doesn't apply an f to a failed Try returned by one f before."
(t/is (= 3
(try/val (try/bind (try/fail 3)
(comp try/fail inc)
(comp try/succeed inc)))))))

(t/deftest bind->-test
(t/testing "bind-> applies multiple functions to a successful Try value."
(t/is (= 5
(try/val (try/bind-> (try/succeed 3)
((comp try/succeed inc))
((comp try/succeed inc))))))
(t/is (= 3
(try/val (try/bind-> (try/fail 3)
((comp try/succeed inc))
((comp try/succeed inc))))))))

(t/deftest sequence-test
(t/testing "sequence transform a collection of successes into a success of collection."
(t/is (= [1 2 3]
Expand All @@ -201,3 +180,19 @@
(t/testing "sequence returns the empty vector if the collection is empty"
(t/is (= []
(try/val (try/sequence []))))))

(t/deftest into-test
(t/testing "into transform a collection of successes into a success of collection."
(t/is (= {:one 1, :two 2}
(try/val (try/into {} [(try/succeed [:one 1])
(try/succeed [:two 2])])))))
(t/testing "into transform a collection with at least one failure into a failure of collection of failure values."
(t/is (= {:one 1, :two 2}
(try/val (try/into {} [(try/succeed 1)
(try/fail [:one 1])
(try/fail [:two 2])])))))
(t/testing "into returns the given collection if the collection of trys is empty"
(t/is (= []
(try/val (try/into [] []))))
(t/is (= {}
(try/val (try/into {} []))))))

0 comments on commit f568b43

Please sign in to comment.