git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
add withValue, typeclass instances, and ParOmLayer Alt/Plus/Alternative
2b3b742Mark
.psc-ide-portnew file+1
@@ -0,0 +1 @@
1+15500
.spec-results+96−33
@@ -1,232 +1,295 @@
1[
1[
2+ [
3+ "Alt falls back to second layer on failure",
4+ {
5+ "timestamp": "1775221839085.0",
6+ "success": true
7+ }
8+ ],
2 [
9 [
3 "Automatic Memoization builds the same layer only once when used in two branches",
10 "Automatic Memoization builds the same layer only once when used in two branches",
4 {
11 {
5− "timestamp": "1771460758484.0",
12+ "timestamp": "1775221839085.0",
6 "success": true
13 "success": true
7 }
14 }
8 ],
15 ],
9 [
16 [
10 "Automatic Memoization memoized bracketLayer acquires and releases only once",
17 "Automatic Memoization memoized bracketLayer acquires and releases only once",
11 {
18 {
12− "timestamp": "1771460758484.0",
19+ "timestamp": "1775221839085.0",
13 "success": true
20 "success": true
14 }
21 }
15 ],
22 ],
16 [
23 [
17 "Automatic Memoization memoized scoped layer runs finalizer only once",
24 "Automatic Memoization memoized scoped layer runs finalizer only once",
18 {
25 {
19− "timestamp": "1771460758484.0",
26+ "timestamp": "1775221839085.0",
20 "success": true
27 "success": true
21 }
28 }
22 ],
29 ],
23 [
30 [
24 "Composition proves Row.Nub deduplicates shared requirements at type level",
31 "Composition proves Row.Nub deduplicates shared requirements at type level",
25 {
32 {
26− "timestamp": "1771460758484.0",
33+ "timestamp": "1775221839085.0",
27 "success": true
34 "success": true
28 }
35 }
29 ],
36 ],
30 [
37 [
31 "Composition proves deduplication at runtime via shared Ref",
38 "Composition proves deduplication at runtime via shared Ref",
32 {
39 {
33− "timestamp": "1771460758484.0",
40+ "timestamp": "1775221839085.0",
34 "success": true
41 "success": true
35 }
42 }
36 ],
43 ],
37 [
44 [
38 "Error Channel composes layers with different error types",
45 "Error Channel composes layers with different error types",
39 {
46 {
40− "timestamp": "1771460758484.0",
47+ "timestamp": "1775221839085.0",
41 "success": true
48 "success": true
42 }
49 }
43 ],
50 ],
44 [
51 [
45 "Error Channel propagates errors from the first layer",
52 "Error Channel propagates errors from the first layer",
46 {
53 {
47− "timestamp": "1771460758484.0",
54+ "timestamp": "1775221839085.0",
48 "success": true
55 "success": true
49 }
56 }
50 ],
57 ],
51 [
58 [
52 "Error Channel propagates errors from the second layer",
59 "Error Channel propagates errors from the second layer",
53 {
60 {
54− "timestamp": "1771460758484.0",
61+ "timestamp": "1775221839085.0",
55 "success": true
62 "success": true
56 }
63 }
57 ],
64 ],
58 [
65 [
59 "Error Channel propagates errors through vertical composition",
66 "Error Channel propagates errors through vertical composition",
60 {
67 {
61− "timestamp": "1771460758484.0",
68+ "timestamp": "1775221839085.0",
62 "success": true
69 "success": true
63 }
70 }
64 ],
71 ],
65 [
72 [
66 "Error Channel scoped layer error triggers finalizers",
73 "Error Channel scoped layer error triggers finalizers",
67 {
74 {
68− "timestamp": "1771460758484.0",
75+ "timestamp": "1775221839085.0",
69 "success": true
76 "success": true
70 }
77 }
71 ],
78 ],
72 [
79 [
73 "Error Channel scoped layers with errors run via runScopedWith",
80 "Error Channel scoped layers with errors run via runScopedWith",
74 {
81 {
75− "timestamp": "1771460758484.0",
82+ "timestamp": "1775221839085.0",
76 "success": true
83 "success": true
77 }
84 }
78 ],
85 ],
79 [
86 [
80 "Functor / Apply / Bind applies two layers",
87 "Functor / Apply / Bind applies two layers",
81 {
88 {
82− "timestamp": "1771460758484.0",
89+ "timestamp": "1775221839085.0",
83 "success": true
90 "success": true
84 }
91 }
85 ],
92 ],
86 [
93 [
87 "Functor / Apply / Bind binds layers sequentially",
94 "Functor / Apply / Bind binds layers sequentially",
88 {
95 {
89− "timestamp": "1771460758484.0",
96+ "timestamp": "1775221839085.0",
90 "success": true
97 "success": true
91 }
98 }
92 ],
99 ],
93 [
100 [
94 "Functor / Apply / Bind maps over layer output",
101 "Functor / Apply / Bind maps over layer output",
95 {
102 {
96− "timestamp": "1771460758484.0",
103+ "timestamp": "1775221839085.0",
104+ "success": true
105+ }
106+ ],
107+ [
108+ "Functor / Apply / Bind runs layers against a superset context",
109+ {
110+ "timestamp": "1775221839085.0",
97 "success": true
111 "success": true
98 }
112 }
99 ],
113 ],
100 [
114 [
101 "Layer recovering does not retry on non-matching errors",
115 "Layer recovering does not retry on non-matching errors",
102 {
116 {
103− "timestamp": "1771460758484.0",
117+ "timestamp": "1775221839085.0",
104 "success": true
118 "success": true
105 }
119 }
106 ],
120 ],
107 [
121 [
108 "Layer recovering only retries the specified error in a multi-error layer",
122 "Layer recovering only retries the specified error in a multi-error layer",
109 {
123 {
110− "timestamp": "1771460758484.0",
124+ "timestamp": "1775221839085.0",
111 "success": true
125 "success": true
112 }
126 }
113 ],
127 ],
114 [
128 [
115 "Layer recovering preserves the error when retries are exhausted",
129 "Layer recovering preserves the error when retries are exhausted",
116 {
130 {
117− "timestamp": "1771460758484.0",
131+ "timestamp": "1775221839085.0",
118 "success": true
132 "success": true
119 }
133 }
120 ],
134 ],
121 [
135 [
122 "Layer recovering retries layer construction on matching errors",
136 "Layer recovering retries layer construction on matching errors",
123 {
137 {
124− "timestamp": "1771460758484.0",
138+ "timestamp": "1775221839085.0",
125 "success": true
139 "success": true
126 }
140 }
127 ],
141 ],
128 [
142 [
129 "Layer recovering uses RetryStatus in the check",
143 "Layer recovering uses RetryStatus in the check",
130 {
144 {
131− "timestamp": "1771460758484.0",
145+ "timestamp": "1775221839085.0",
132 "success": true
146 "success": true
133 }
147 }
134 ],
148 ],
135 [
149 [
136 "Layer repeating repeats layer construction while condition holds",
150 "Layer repeating repeats layer construction while condition holds",
137 {
151 {
138− "timestamp": "1771460758484.0",
152+ "timestamp": "1775221839085.0",
139 "success": true
153 "success": true
140 }
154 }
141 ],
155 ],
142 [
156 [
143 "Layer repeating stops when policy exhausted",
157 "Layer repeating stops when policy exhausted",
144 {
158 {
145− "timestamp": "1771460758484.0",
159+ "timestamp": "1775221839085.0",
160+ "success": true
161+ }
162+ ],
163+ [
164+ "MonadAsk / MonadReader ask reads the context directly",
165+ {
166+ "timestamp": "1775221839085.0",
167+ "success": true
168+ }
169+ ],
170+ [
171+ "MonadAsk / MonadReader local modifies context for a layer",
172+ {
173+ "timestamp": "1775221839085.0",
174+ "success": true
175+ }
176+ ],
177+ [
178+ "MonadEffect / MonadAff liftEffect works directly in layer bind",
179+ {
180+ "timestamp": "1775221839085.0",
181+ "success": true
182+ }
183+ ],
184+ [
185+ "MonadThrow / MonadError throwError raises into the error channel",
186+ {
187+ "timestamp": "1775221839085.0",
146 "success": true
188 "success": true
147 }
189 }
148 ],
190 ],
149 [
191 [
150 "Parallel runs layers in parallel via parApply",
192 "Parallel runs layers in parallel via parApply",
151 {
193 {
152− "timestamp": "1771460758484.0",
194+ "timestamp": "1775221839085.0",
153 "success": true
195 "success": true
154 }
196 }
155 ],
197 ],
156 [
198 [
157 "Scoped Layers combines scoped and non-scoped layers",
199 "Scoped Layers combines scoped and non-scoped layers",
158 {
200 {
159− "timestamp": "1771460758484.0",
201+ "timestamp": "1775221839085.0",
160 "success": true
202 "success": true
161 }
203 }
162 ],
204 ],
163 [
205 [
164 "Scoped Layers fresh layer builds twice even when same base layer",
206 "Scoped Layers fresh layer builds twice even when same base layer",
165 {
207 {
166− "timestamp": "1771460758484.0",
208+ "timestamp": "1775221839085.0",
167 "success": true
209 "success": true
168 }
210 }
169 ],
211 ],
170 [
212 [
171 "Scoped Layers runs finalizers in reverse order on success",
213 "Scoped Layers runs finalizers in reverse order on success",
172 {
214 {
173− "timestamp": "1771460758484.0",
215+ "timestamp": "1775221839085.0",
174 "success": true
216 "success": true
175 }
217 }
176 ],
218 ],
177 [
219 [
178 "Scoped Layers runs finalizers when the callback throws",
220 "Scoped Layers runs finalizers when the callback throws",
179 {
221 {
180− "timestamp": "1771460758484.0",
222+ "timestamp": "1775221839085.0",
181 "success": true
223 "success": true
182 }
224 }
183 ],
225 ],
184 [
226 [
185 "Scoped Layers threads finalizers through vertical composition (provide)",
227 "Scoped Layers threads finalizers through vertical composition (provide)",
186 {
228 {
187− "timestamp": "1771460758484.0",
229+ "timestamp": "1775221839085.0",
188 "success": true
230 "success": true
189 }
231 }
190 ],
232 ],
191 [
233 [
192 "Scoped Layers works with bracketLayer",
234 "Scoped Layers works with bracketLayer",
193 {
235 {
194− "timestamp": "1771460758484.0",
236+ "timestamp": "1775221839085.0",
237+ "success": true
238+ }
239+ ],
240+ [
241+ "Semigroup appends layer outputs",
242+ {
243+ "timestamp": "1775221839085.0",
244+ "success": true
245+ }
246+ ],
247+ [
248+ "combinePure combines a plain record with a layer without needing VTA",
249+ {
250+ "timestamp": "1775214062703.0",
195 "success": true
251 "success": true
196 }
252 }
197 ],
253 ],
198 [
254 [
199 "wireLayers memoizes shared dependencies within wireLayers",
255 "wireLayers memoizes shared dependencies within wireLayers",
200 {
256 {
201− "timestamp": "1771460758484.0",
257+ "timestamp": "1775221839085.0",
202 "success": true
258 "success": true
203 }
259 }
204 ],
260 ],
205 [
261 [
206 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
262 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
207 {
263 {
208− "timestamp": "1771460758484.0",
264+ "timestamp": "1775221839085.0",
209 "success": true
265 "success": true
210 }
266 }
211 ],
267 ],
212 [
268 [
213 "wireLayers wires a diamond dependency",
269 "wireLayers wires a diamond dependency",
214 {
270 {
215− "timestamp": "1771460758484.0",
271+ "timestamp": "1775221839085.0",
216 "success": true
272 "success": true
217 }
273 }
218 ],
274 ],
219 [
275 [
220 "wireLayers wires a simple linear chain (A -> B -> C)",
276 "wireLayers wires a simple linear chain (A -> B -> C)",
221 {
277 {
222− "timestamp": "1771460758484.0",
278+ "timestamp": "1775221839085.0",
223 "success": true
279 "success": true
224 }
280 }
225 ],
281 ],
226 [
282 [
227 "wireLayers wires independent layers with no inter-dependencies",
283 "wireLayers wires independent layers with no inter-dependencies",
228 {
284 {
229− "timestamp": "1771460758484.0",
285+ "timestamp": "1775221839085.0",
286+ "success": true
287+ }
288+ ],
289+ [
290+ "withValue combines a plain record with a layer without needing VTA",
291+ {
292+ "timestamp": "1775221839085.0",
230 "success": true
293 "success": true
231 }
294 }
232 ]
295 ]
spago.lock+5−2
@@ -14,6 +14,9 @@
14 {
14 {
15 "arrays": ">=7.3.0 <8.0.0"
15 "arrays": ">=7.3.0 <8.0.0"
16 },
16 },
17+ {
18+ "control": ">=6.0.0 <7.0.0"
19+ },
17 {
20 {
18 "effect": ">=4.0.0 <5.0.0"
21 "effect": ">=4.0.0 <5.0.0"
19 },
22 },
@@ -998,8 +1001,8 @@
998 },
1001 },
999 "spec": {
1002 "spec": {
1000 "type": "registry",
1003 "type": "registry",
1001− "version": "8.1.1",
1004+ "version": "8.1.2",
1002− "integrity": "sha256-b6qjDb7lUJGLV+WEoywLu6BCXC4zGpfbN43mTL+vBeI=",
1005+ "integrity": "sha256-klPPJ6lBQI1CGOAdLJ+ZoGRt8XZippkTKjn53BrSkL0=",
1003 "dependencies": [
1006 "dependencies": [
1004 "aff",
1007 "aff",
1005 "ansi",
1008 "ansi",
spago.yaml+2−1
@@ -2,7 +2,7 @@ workspace: {}
2package:
2package:
3 name: yoga-om-layer
3 name: yoga-om-layer
4 publish:
4 publish:
5− version: 2.0.0
5+ version: 2.1.0
6 license: MIT
6 license: MIT
7 location:
7 location:
8 githubOwner: rowtype-yoga
8 githubOwner: rowtype-yoga
@@ -11,6 +11,7 @@ package:
11 - aff: ">=8.0.0 <9.0.0"
11 - aff: ">=8.0.0 <9.0.0"
12 - aff-retry: ">=2.0.0 <3.0.0"
12 - aff-retry: ">=2.0.0 <3.0.0"
13 - arrays: ">=7.3.0 <8.0.0"
13 - arrays: ">=7.3.0 <8.0.0"
14+ - control: ">=6.0.0 <7.0.0"
14 - effect: ">=4.0.0 <5.0.0"
15 - effect: ">=4.0.0 <5.0.0"
15 - exceptions: ">=6.1.0 <7.0.0"
16 - exceptions: ">=6.1.0 <7.0.0"
16 - foldable-traversable: ">=6.0.0 <7.0.0"
17 - foldable-traversable: ">=6.0.0 <7.0.0"
src/Yoga/Om/Layer.purs+66−3
@@ -15,6 +15,7 @@ module Yoga.Om.Layer
15 , withScopedWith
15 , withScopedWith
16 , scoped
16 , scoped
17 , combineRequirements
17 , combineRequirements
18+ , withValue
18 , provide
19 , provide
19 , (>->)
20 , (>->)
20 , recovering
21 , recovering
@@ -52,10 +53,19 @@ module Yoga.Om.Layer
52
53
53import Prelude
54import Prelude
54
55
56+import Control.Alt (class Alt)
57+import Control.Alt as Alt
58+import Control.Alternative (class Alternative)
55−import Control.Monad.Error.Class (catchError, throwError)
59+import Control.Monad.Error.Class (class MonadError, class MonadThrow, catchError, throwError)
56−import Data.Symbol (class IsSymbol)
60+import Control.Monad.Reader (class MonadAsk, class MonadReader)
61+import Control.Monad.Reader as Reader
62+import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM)
57import Control.Parallel (class Parallel)
63import Control.Parallel (class Parallel)
58import Control.Parallel as Parallel
64import Control.Parallel as Parallel
65+import Control.Plus (class Plus)
66+import Control.Plus as Plus
67+import Data.Symbol (class IsSymbol)
68+import Data.Variant (Variant)
59import Data.Array as Array
69import Data.Array as Array
60import Data.Foldable (for_)
70import Data.Foldable (for_)
61import Data.Map (Map)
71import Data.Map (Map)
@@ -66,7 +76,10 @@ import Data.Variant (class VariantMatchCases, onMatch)
66import Effect (Effect)
76import Effect (Effect)
67import Effect.Aff (Aff, bracket)
77import Effect.Aff (Aff, bracket)
68import Effect.Aff.Retry (RetryPolicyM, RetryStatus, applyAndDelay, defaultRetryStatus)
78import Effect.Aff.Retry (RetryPolicyM, RetryStatus, applyAndDelay, defaultRetryStatus)
79+import Effect.Aff.Class (class MonadAff)
80+import Effect.Aff.Class as Aff
69−import Effect.Class (liftEffect)
81+import Effect.Class (class MonadEffect, liftEffect)
82+import Effect.Class (class MonadEffect, liftEffect) as Effect
70import Effect.Exception (Error)
83import Effect.Exception (Error)
71import Effect.Ref (Ref)
84import Effect.Ref (Ref)
72import Effect.Ref as Ref
85import Effect.Ref as Ref
@@ -86,6 +99,7 @@ import Type.Equality (class TypeEquals)
86import Unsafe.Coerce (unsafeCoerce)
99import Unsafe.Coerce (unsafeCoerce)
87import Yoga.Om (Om, ParOm)
100import Yoga.Om (Om, ParOm)
88import Yoga.Om as Om
101import Yoga.Om as Om
102+import Yoga.Om.Error (Exception)
89
103
90-- =============================================================================
104-- =============================================================================
91-- Scope — a first-class representation of resource lifetime
105-- Scope — a first-class representation of resource lifetime
@@ -174,6 +188,36 @@ instance Bind (OmLayer req err) where
174
188
175instance Monad (OmLayer req err)
189instance Monad (OmLayer req err)
176
190
191+instance MonadEffect (OmLayer req err) where
192+ liftEffect eff = OmLayer (freshId unit) (Effect.liftEffect eff)
193+
194+instance MonadAff (OmLayer req err) where
195+ liftAff aff = OmLayer (freshId unit) (Aff.liftAff aff)
196+
197+instance MonadAsk (Record req) (OmLayer req err) where
198+ ask = OmLayer (freshId unit) Reader.ask
199+
200+instance MonadReader (Record req) (OmLayer req err) where
201+ local f layer = OmLayer (freshId unit) (Reader.local f (buildLayer layer))
202+
203+instance MonadThrow (Variant (Exception err)) (OmLayer req err) where
204+ throwError e = OmLayer (freshId unit) (throwError e)
205+
206+instance MonadError (Variant (Exception err)) (OmLayer req err) where
207+ catchError layer handler = OmLayer (freshId unit) (catchError (buildLayer layer) (\e -> buildLayer (handler e)))
208+
209+instance MonadRec (OmLayer req err) where
210+ tailRecM f a = OmLayer (freshId unit) (tailRecM (\x -> buildLayer (f x)) a)
211+
212+instance Semigroup a => Semigroup (OmLayer req err a) where
213+ append a b = OmLayer (freshId unit) (append (buildLayer a) (buildLayer b))
214+
215+instance Alt (OmLayer req err) where
216+ alt a b = OmLayer (freshId unit) (Alt.alt (buildLayer a) (buildLayer b))
217+
218+instance Plus (OmLayer req err) where
219+ empty = OmLayer (freshId unit) Plus.empty
220+
177instance Functor (ParOmLayer req err) where
221instance Functor (ParOmLayer req err) where
178 map f (ParOmLayer p) = ParOmLayer (map f p)
222 map f (ParOmLayer p) = ParOmLayer (map f p)
179
223
@@ -183,6 +227,14 @@ instance Apply (ParOmLayer req err) where
183instance Applicative (ParOmLayer req err) where
227instance Applicative (ParOmLayer req err) where
184 pure a = ParOmLayer (pure a)
228 pure a = ParOmLayer (pure a)
185
229
230+instance Alt (ParOmLayer req err) where
231+ alt (ParOmLayer a) (ParOmLayer b) = ParOmLayer (Alt.alt a b)
232+
233+instance Plus (ParOmLayer req err) where
234+ empty = ParOmLayer Plus.empty
235+
236+instance Alternative (ParOmLayer req err)
237+
186instance Parallel (ParOmLayer req err) (OmLayer req err) where
238instance Parallel (ParOmLayer req err) (OmLayer req err) where
187 parallel (OmLayer _ om) = ParOmLayer (Parallel.parallel om)
239 parallel (OmLayer _ om) = ParOmLayer (Parallel.parallel om)
188 sequential (ParOmLayer p) = OmLayer (freshId unit) (Parallel.sequential p)
240 sequential (ParOmLayer p) = OmLayer (freshId unit) (Parallel.sequential p)
@@ -444,6 +496,17 @@ combineRequirements layer1 layer2 = OmLayer (freshId unit) do
444 rec2 <- Om.expand (buildLayer layer2)
496 rec2 <- Om.expand (buildLayer layer2)
445 pure (Record.merge rec1 rec2)
497 pure (Record.merge rec1 rec2)
446
498
499+withValue
500+ :: forall req err prov1 prov2 provMerged
501+ . Union prov1 prov2 provMerged
502+ => Nub provMerged provMerged
503+ => Record prov1
504+ -> OmLayer req err (Record prov2)
505+ -> OmLayer req err (Record provMerged)
506+withValue defaults layer = OmLayer (freshId unit) do
507+ rec <- buildLayer layer
508+ pure (Record.merge defaults rec)
509+
447-- =============================================================================
510-- =============================================================================
448-- Vertical composition — feed output of one layer into input of another
511-- Vertical composition — feed output of one layer into input of another
449-- =============================================================================
512-- =============================================================================
test/Test/Yoga/Om/Layer/InstancesSpec.purs+81
@@ -2,11 +2,14 @@ module Test.Yoga.Om.Layer.InstancesSpec where
2
2
3import Prelude
3import Prelude
4
4
5+import Control.Alt ((<|>))
6+import Control.Monad.Reader (ask, local)
5import Control.Parallel (parApply)
7import Control.Parallel (parApply)
6import Effect.Class (liftEffect)
8import Effect.Class (liftEffect)
7import Effect.Ref as Ref
9import Effect.Ref as Ref
8import Test.Spec (Spec, describe, it)
10import Test.Spec (Spec, describe, it)
9import Test.Spec.Assertions (shouldEqual)
11import Test.Spec.Assertions (shouldEqual)
12+import Effect.Exception as Exception
10import Yoga.Om as Om
13import Yoga.Om as Om
11import Yoga.Om.Layer (OmLayer, makeLayer, runLayer)
14import Yoga.Om.Layer (OmLayer, makeLayer, runLayer)
12
15
@@ -58,6 +61,84 @@ spec = do
58 result <- Om.runOm ctx { exception: \_ -> pure { out: 0 } } (runLayer ctx layer)
61 result <- Om.runOm ctx { exception: \_ -> pure { out: 0 } } (runLayer ctx layer)
59 result.out `shouldEqual` 42
62 result.out `shouldEqual` 42
60
63
64+ describe "MonadEffect / MonadAff" do
65+
66+ it "liftEffect works directly in layer bind" do
67+ ref <- liftEffect $ Ref.new 0
68+ let
69+ layer :: OmLayer () () Int
70+ layer = do
71+ liftEffect $ Ref.write 42 ref
72+ liftEffect $ Ref.read ref
73+
74+ result <- Om.runOm {} { exception: \_ -> pure 0 } (runLayer {} layer)
75+ result `shouldEqual` 42
76+
77+ describe "MonadAsk / MonadReader" do
78+
79+ it "ask reads the context directly" do
80+ let
81+ layer :: OmLayer (port :: Int) () Int
82+ layer = do
83+ ctx <- ask
84+ pure ctx.port
85+
86+ result <- Om.runOm { port: 8080 } { exception: \_ -> pure 0 } (runLayer { port: 8080 } layer)
87+ result `shouldEqual` 8080
88+
89+ it "local modifies context for a layer" do
90+ let
91+ inner :: OmLayer (port :: Int) () Int
92+ inner = do
93+ ctx <- ask
94+ pure ctx.port
95+
96+ layer :: OmLayer (port :: Int) () Int
97+ layer = local (\r -> r { port = r.port + 1 }) inner
98+
99+ result <- Om.runOm { port: 8080 } { exception: \_ -> pure 0 } (runLayer { port: 8080 } layer)
100+ result `shouldEqual` 8081
101+
102+ describe "MonadThrow / MonadError" do
103+
104+ it "throwError raises into the error channel" do
105+ let
106+ layer :: OmLayer () (myErr :: String) Int
107+ layer = Om.throw { myErr: "boom" }
108+
109+ result <- Om.runOm {}
110+ { exception: \_ -> pure (-1), myErr: \_ -> pure (-2) }
111+ (runLayer {} layer)
112+ result `shouldEqual` (-2)
113+
114+ describe "Alt" do
115+
116+ it "falls back to second layer on failure" do
117+ let
118+ failing :: OmLayer () () Int
119+ failing = Om.throw { exception: Exception.error "nope" }
120+
121+ fallback :: OmLayer () () Int
122+ fallback = pure 99
123+
124+ layer = failing <|> fallback
125+
126+ result <- Om.runOm {} { exception: \_ -> pure (-1) } (runLayer {} layer)
127+ result `shouldEqual` 99
128+
129+ describe "Semigroup" do
130+
131+ it "appends layer outputs" do
132+ let
133+ a :: OmLayer () () String
134+ a = pure "hello"
135+
136+ b :: OmLayer () () String
137+ b = pure " world"
138+
139+ result <- Om.runOm {} { exception: \_ -> pure "" } (runLayer {} (a <> b))
140+ result `shouldEqual` "hello world"
141+
61 describe "Parallel" do
142 describe "Parallel" do
62
143
63 it "runs layers in parallel via parApply" do
144 it "runs layers in parallel via parApply" do
test/Test/Yoga/Om/Layer/PureInferenceSpec.pursnew file+22
@@ -0,0 +1,22 @@
1+module Test.Yoga.Om.Layer.PureInferenceSpec where
2+
3+import Prelude
4+
5+import Test.Spec (Spec, describe, it)
6+import Test.Spec.Assertions (shouldEqual)
7+import Yoga.Om as Om
8+import Yoga.Om.Layer (OmLayer, withValue, makeLayer, runLayer)
9+
10+spec :: Spec Unit
11+spec = describe "withValue" do
12+
13+ it "combines a plain record with a layer without needing VTA" do
14+ let
15+ layer :: OmLayer () () { value :: Int }
16+ layer = makeLayer (pure { value: 42 })
17+
18+ result = layer # withValue { extra: "hello" }
19+
20+ out <- Om.runOm {} { exception: \_ -> pure { value: -1, extra: "" } } (runLayer {} result)
21+ out.value `shouldEqual` 42
22+ out.extra `shouldEqual` "hello"