Skip to content

Commit 128c04f

Browse files
authored
remove dependency from cats to play nice with protocols (#36)
* import cats to abide by the peace treaty * tests passing * remove bloat * lint * changelog/bump * remove warning * ok linter * remove context * remove context from impls
1 parent 815e473 commit 128c04f

11 files changed

Lines changed: 301 additions & 136 deletions

File tree

CHANGELOG.md

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

3+
## 1.19.0 / 2024-08-15
4+
- Remove applicative engine dependency from funcool/cats
5+
36
## 1.18.1 / 2024-07-16
47
- Fix virtual-future applicative engine error handling so that ExecutionExceptions are always unwrapped
58
- Improve ergonomics when an unknown engine is specified

project.clj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(defproject dev.nu/nodely "1.18.1"
1+
(defproject dev.nu/nodely "1.19.0"
22
:description "Decoupling data fetching from data dependency declaration"
33
:url "https://github.com/nubank/nodely"
44
:license {:name "MIT"}
@@ -9,7 +9,6 @@
99
:dependencies [[org.clojure/clojure "1.10.3"]
1010
[aysylu/loom "1.0.2"]
1111
[org.clojure/core.async "1.5.648"]
12-
[funcool/cats "2.4.2"]
1312
[funcool/promesa "10.0.594"]
1413
[manifold "0.1.9-alpha5"]
1514
[prismatic/schema "1.1.12"]]

src/nodely/engine/applicative.clj

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,47 @@
11
(ns nodely.engine.applicative
22
(:refer-clojure :exclude [eval sequence])
33
(:require
4-
[cats.context :as context]
5-
[cats.core :as m]
64
[clojure.core.async :as async]
75
[clojure.pprint :as pp]
86
[nodely.data :as data]
7+
[nodely.engine.applicative.core :as app]
98
[nodely.engine.applicative.promesa :as promesa]
109
[nodely.engine.applicative.protocols :as protocols]
1110
[nodely.engine.core :as core]
1211
[nodely.engine.lazy-env :as lazy-env]))
1312

1413
(prefer-method pp/simple-dispatch clojure.lang.IPersistentMap clojure.lang.IDeref)
1514

16-
;; sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
17-
(defn sequence
18-
[chs]
19-
(reduce (m/lift-a 2 conj) (m/pure []) chs))
20-
2115
(defn in-context?
2216
[x context]
23-
(and (satisfies? cats.protocols/Contextual x)
24-
(= context (cats.protocols/-get-context x))))
17+
(= context (protocols/-get-context x)))
2518

2619
(declare eval-node)
2720

2821
(defn eval-sequence
29-
[node lazy-env opts]
22+
[node lazy-env {::keys [context] :as opts}]
3023
(let [in-key (::data/input node)
31-
mf (m/fmap ::data/value
32-
(eval-node (::data/process-node node) lazy-env opts))
24+
mf (app/fmap ::data/value
25+
(eval-node (::data/process-node node) lazy-env opts))
3326
mseq (get lazy-env in-key)]
3427
(->> mseq
35-
(m/fmap (comp m/sequence
36-
(partial map
37-
(comp (partial m/fapply mf)
38-
m/pure))
39-
::data/value))
40-
m/join
41-
(m/fmap data/value))))
28+
(app/fmap (comp (partial app/sequence context)
29+
(partial map
30+
(comp (partial app/fapply mf)
31+
(partial app/pure context)))
32+
::data/value))
33+
app/join
34+
(app/fmap data/value))))
4235

4336
(defn eval-branch
4437
[{::data/keys [condition truthy falsey]}
4538
lazy-env
4639
opts]
47-
(m/alet [condition-value-node (eval-node condition lazy-env opts)
48-
result (if (::data/value condition-value-node)
49-
(eval-node truthy lazy-env opts)
50-
(eval-node falsey lazy-env opts))]
51-
result))
40+
(app/bind (eval-node condition lazy-env opts)
41+
(fn [condition-value-node]
42+
(if (:nodely.data/value condition-value-node)
43+
(eval-node truthy lazy-env opts)
44+
(eval-node falsey lazy-env opts)))))
5245

5346
(defn noop-validate
5447
[return _]
@@ -60,60 +53,59 @@
6053
tags (::data/tags leaf)
6154
f (with-meta (::data/fn leaf)
6255
{::data/tags tags})]
63-
(m/mlet [v (protocols/apply-fn f (m/fmap #(core/prepare-inputs deps-keys (zipmap deps-keys %))
64-
(sequence (mapv #(get lazy-env %) deps-keys))))]
65-
(if (in-context? v context)
66-
(m/fmap data/value v)
67-
(m/pure context (data/value v))))))
56+
(app/mlet [v (app/apply-fn f (app/fmap #(core/prepare-inputs deps-keys (zipmap deps-keys %))
57+
(app/sequence context (mapv #(get lazy-env %) deps-keys))))]
58+
(if (in-context? v context)
59+
(app/fmap data/value v)
60+
(app/pure context (data/value v))))))
6861

6962
(defn eval-node
7063
[node lazy-env opts]
7164
(case (::data/type node)
72-
:value (m/pure node)
65+
:value (app/pure (::context opts) node)
7366
:leaf (eval-leaf node lazy-env opts)
7467
:branch (eval-branch node lazy-env opts)
7568
:sequence (eval-sequence node lazy-env opts)))
7669

7770
(defn eval-in-context
7871
[env k lazy-env {::keys [fvalidate] :as opts}]
79-
(context/with-context (::context opts)
80-
(let [node (get env k)
81-
node-ret (eval-node node lazy-env opts)
82-
validator (or fvalidate noop-validate)]
83-
(validator node-ret node))))
72+
(let [node (get env k)
73+
node-ret (eval-node node lazy-env opts)
74+
validator (or fvalidate noop-validate)]
75+
(validator node-ret node)))
8476

8577
(defn eval-key
8678
([env k]
8779
(eval-key env k {}))
8880
([env k opts]
8981
(let [opts (merge {::context promesa/context} opts)
9082
lazy-env (lazy-env/lazy-env env eval-in-context opts)]
91-
(::data/value (m/extract (get lazy-env k))))))
83+
(::data/value (protocols/-extract (get lazy-env k))))))
9284

9385
(defn eval-key-contextual
9486
([env k]
9587
(eval-key env k {}))
9688
([env k opts]
9789
(let [opts (merge {::context promesa/context} opts)
9890
lazy-env (lazy-env/lazy-env env eval-in-context opts)]
99-
(m/fmap ::data/value (get lazy-env k)))))
91+
(app/fmap ::data/value (get lazy-env k)))))
10092

10193
(defn eval-key-channel
10294
([env k]
10395
(eval-key-channel env k {}))
10496
([env k opts]
10597
(let [contextual-v (eval-key-contextual env k opts)
10698
chan (async/promise-chan)]
107-
(m/fmap (partial async/put! chan) contextual-v)
99+
(app/fmap (partial async/put! chan) contextual-v)
108100
chan)))
109101

110102
(defn eval
111103
([env k]
112104
(eval env k {::context promesa/context}))
113105
([env k opts]
114106
(let [lazy-env (lazy-env/lazy-env env eval-in-context opts)]
115-
(m/extract (get lazy-env k)) ;; ensures k is resolved
107+
(protocols/-extract (get lazy-env k)) ;; ensures k is resolved
116108
(merge env
117-
(reduce (fn [acc [k v]] (assoc acc k (m/extract v)))
109+
(reduce (fn [acc [k v]] (assoc acc k (protocols/-extract v)))
118110
{}
119111
(lazy-env/scheduled-nodes lazy-env))))))
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
(ns nodely.engine.applicative.core
2+
(:refer-clojure :exclude [sequence])
3+
(:require
4+
[nodely.engine.applicative.protocols :as p]))
5+
6+
(defn throw-illegal-argument
7+
{:no-doc true :internal true}
8+
[^String text]
9+
(throw (IllegalArgumentException. text)))
10+
11+
;; CONTEXT STUFF HERE
12+
13+
(defprotocol Contextual
14+
"Abstraction that establishes a concrete type as a member of a context.
15+
16+
A great example is the Maybe monad type Just. It implements
17+
this abstraction to establish that Just is part of
18+
the Maybe monad."
19+
(-get-context [_] "Get the context associated with the type."))
20+
21+
(extend-protocol p/Contextual
22+
java.lang.Object
23+
(-get-context [_] nil))
24+
25+
(defn infer
26+
"Given an optional value infer its context. If context is already set, it
27+
is returned as is without any inference operation."
28+
{:no-doc true}
29+
[v]
30+
(if-let [context (p/-get-context v)]
31+
context
32+
(throw-illegal-argument
33+
(str "No context is set and it can not be automatically "
34+
"resolved from provided value"))))
35+
36+
;; END CONTEXT STUFF
37+
38+
;; FUNCTOR STUFF
39+
(defn fmap
40+
"Apply a function `f` to the value wrapped in functor `fv`,
41+
preserving the context type."
42+
[f fv]
43+
(let [ctx (infer fv)]
44+
(p/-fmap ctx f fv)))
45+
46+
;; MONAD STUFF
47+
(defn bind
48+
"Given a monadic value `mv` and a function `f`,
49+
apply `f` to the unwrapped value of `mv`.
50+
51+
(bind (either/right 1) (fn [v]
52+
(return (inc v))))
53+
;; => #<Right [2]>
54+
55+
For convenience, you may prefer to use the `mlet` macro,
56+
which provides a beautiful, `let`-like syntax for
57+
composing operations with the `bind` function."
58+
[mv f]
59+
(let [ctx (infer mv)]
60+
(p/-mbind ctx mv f)))
61+
62+
(defn return
63+
"This is a monad version of `pure` and works
64+
identically to it."
65+
[ctx v]
66+
(p/-mreturn ctx v))
67+
68+
(defn join
69+
"Remove one level of monadic structure.
70+
This is the same as `(bind mv identity)`."
71+
[mv]
72+
(bind mv identity))
73+
74+
(defmacro mlet
75+
"Monad composition macro that works like Clojure's
76+
`let`. This facilitates much easier composition of
77+
monadic computations.
78+
79+
Let's see an example to understand how it works.
80+
This code uses bind to compose a few operations:
81+
82+
(bind (just 1)
83+
(fn [a]
84+
(bind (just (inc a))
85+
(fn [b]
86+
(return (* b 2))))))
87+
;=> #<Just [4]>
88+
89+
Now see how this code can be made clearer
90+
by using the mlet macro:
91+
92+
(mlet [a (just 1)
93+
b (just (inc a))]
94+
(return (* b 2)))
95+
;=> #<Just [4]>
96+
"
97+
[bindings & body]
98+
(when-not (and (vector? bindings)
99+
(not-empty bindings)
100+
(even? (count bindings)))
101+
(throw (IllegalArgumentException. "bindings has to be a vector with even number of elements.")))
102+
(->> (reverse (partition 2 bindings))
103+
(reduce (fn [acc [l r]] `(bind ~r (fn [~l] ~acc)))
104+
`(do ~@body))))
105+
106+
;; APPLICATIVE STUFF
107+
(defn pure
108+
"Given any value `v`, return it wrapped in
109+
the default/effect-free context.
110+
111+
This is a multi-arity function that with arity `pure/1`
112+
uses the dynamic scope to resolve the current
113+
context. With `pure/2`, you can force a specific context
114+
value.
115+
116+
Example:
117+
118+
(with-context either/context
119+
(pure 1))
120+
;; => #<Right [1]>
121+
122+
(pure either/context 1)
123+
;; => #<Right [1]>
124+
"
125+
[ctx v]
126+
(p/-pure ctx v))
127+
128+
(defn fapply
129+
"Given a function wrapped in a monadic context `af`,
130+
and a value wrapped in a monadic context `av`,
131+
apply the unwrapped function to the unwrapped value
132+
and return the result, wrapped in the same context as `av`.
133+
134+
This function is variadic, so it can be used like
135+
a Haskell-style left-associative fapply."
136+
[af & avs]
137+
{:pre [(seq avs)]}
138+
(let [ctx (infer af)]
139+
(reduce (partial p/-fapply ctx) af avs)))
140+
141+
(defn sequence
142+
"Given a collection of monadic values, collect
143+
their values in a seq returned in the monadic context.
144+
145+
(require '[cats.context :as ctx]
146+
'[cats.monad.maybe :as maybe]
147+
'[cats.core :as m])
148+
149+
(m/sequence [(maybe/just 2) (maybe/just 3)])
150+
;; => #<Just [[2, 3]]>
151+
152+
(m/sequence [(maybe/nothing) (maybe/just 3)])
153+
;; => #<Nothing>
154+
155+
(ctx/with-context maybe/context
156+
(m/sequence []))
157+
;; => #<Just [()]>
158+
"
159+
[context mvs]
160+
(if (empty? mvs)
161+
(return context ())
162+
(reduce (fn [mvs mv]
163+
(mlet [v mv
164+
vs mvs]
165+
(return context (cons v vs))))
166+
(return context ())
167+
(reverse mvs))))
168+
169+
(defn apply-fn
170+
[f mv]
171+
(let [ctx (infer mv)]
172+
(p/-apply-fn ctx f mv)))

0 commit comments

Comments
 (0)