git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
9 files changed+318−45
since splitsnapshots
unifiedsplit
.psc-ide-port+1−1 .spec-results+96−33 README.md+1 compile-fail-tests/RunLayerOpenRowMissingDep.purs+19 spago.lock+12−6 spago.yaml+4−2 src/Yoga/Om/Layer.purs+70−3 test/Test/Yoga/Om/Layer/InstancesSpec.purs+93 test/Test/Yoga/Om/Layer/PureInferenceSpec.purs+22
.psc-ide-port+1−1
@@ -1 +1 @@
1−15887
1+15500
.spec-results+96−33
@@ -1,232 +1,295 @@
11[
2+ [
3+ "Alt falls back to second layer on failure",
4+ {
5+ "timestamp": "1775221839085.0",
6+ "success": true
7+ }
8+ ],
29 [
310 "Automatic Memoization builds the same layer only once when used in two branches",
411 {
5− "timestamp": "1771460758484.0",
12+ "timestamp": "1775221839085.0",
613 "success": true
714 }
815 ],
916 [
1017 "Automatic Memoization memoized bracketLayer acquires and releases only once",
1118 {
12− "timestamp": "1771460758484.0",
19+ "timestamp": "1775221839085.0",
1320 "success": true
1421 }
1522 ],
1623 [
1724 "Automatic Memoization memoized scoped layer runs finalizer only once",
1825 {
19− "timestamp": "1771460758484.0",
26+ "timestamp": "1775221839085.0",
2027 "success": true
2128 }
2229 ],
2330 [
2431 "Composition proves Row.Nub deduplicates shared requirements at type level",
2532 {
26− "timestamp": "1771460758484.0",
33+ "timestamp": "1775221839085.0",
2734 "success": true
2835 }
2936 ],
3037 [
3138 "Composition proves deduplication at runtime via shared Ref",
3239 {
33− "timestamp": "1771460758484.0",
40+ "timestamp": "1775221839085.0",
3441 "success": true
3542 }
3643 ],
3744 [
3845 "Error Channel composes layers with different error types",
3946 {
40− "timestamp": "1771460758484.0",
47+ "timestamp": "1775221839085.0",
4148 "success": true
4249 }
4350 ],
4451 [
4552 "Error Channel propagates errors from the first layer",
4653 {
47− "timestamp": "1771460758484.0",
54+ "timestamp": "1775221839085.0",
4855 "success": true
4956 }
5057 ],
5158 [
5259 "Error Channel propagates errors from the second layer",
5360 {
54− "timestamp": "1771460758484.0",
61+ "timestamp": "1775221839085.0",
5562 "success": true
5663 }
5764 ],
5865 [
5966 "Error Channel propagates errors through vertical composition",
6067 {
61− "timestamp": "1771460758484.0",
68+ "timestamp": "1775221839085.0",
6269 "success": true
6370 }
6471 ],
6572 [
6673 "Error Channel scoped layer error triggers finalizers",
6774 {
68− "timestamp": "1771460758484.0",
75+ "timestamp": "1775221839085.0",
6976 "success": true
7077 }
7178 ],
7279 [
7380 "Error Channel scoped layers with errors run via runScopedWith",
7481 {
75− "timestamp": "1771460758484.0",
82+ "timestamp": "1775221839085.0",
7683 "success": true
7784 }
7885 ],
7986 [
8087 "Functor / Apply / Bind applies two layers",
8188 {
82− "timestamp": "1771460758484.0",
89+ "timestamp": "1775221839085.0",
8390 "success": true
8491 }
8592 ],
8693 [
8794 "Functor / Apply / Bind binds layers sequentially",
8895 {
89− "timestamp": "1771460758484.0",
96+ "timestamp": "1775221839085.0",
9097 "success": true
9198 }
9299 ],
93100 [
94101 "Functor / Apply / Bind maps over layer output",
95102 {
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",
97111 "success": true
98112 }
99113 ],
100114 [
101115 "Layer recovering does not retry on non-matching errors",
102116 {
103− "timestamp": "1771460758484.0",
117+ "timestamp": "1775221839085.0",
104118 "success": true
105119 }
106120 ],
107121 [
108122 "Layer recovering only retries the specified error in a multi-error layer",
109123 {
110− "timestamp": "1771460758484.0",
124+ "timestamp": "1775221839085.0",
111125 "success": true
112126 }
113127 ],
114128 [
115129 "Layer recovering preserves the error when retries are exhausted",
116130 {
117− "timestamp": "1771460758484.0",
131+ "timestamp": "1775221839085.0",
118132 "success": true
119133 }
120134 ],
121135 [
122136 "Layer recovering retries layer construction on matching errors",
123137 {
124− "timestamp": "1771460758484.0",
138+ "timestamp": "1775221839085.0",
125139 "success": true
126140 }
127141 ],
128142 [
129143 "Layer recovering uses RetryStatus in the check",
130144 {
131− "timestamp": "1771460758484.0",
145+ "timestamp": "1775221839085.0",
132146 "success": true
133147 }
134148 ],
135149 [
136150 "Layer repeating repeats layer construction while condition holds",
137151 {
138− "timestamp": "1771460758484.0",
152+ "timestamp": "1775221839085.0",
139153 "success": true
140154 }
141155 ],
142156 [
143157 "Layer repeating stops when policy exhausted",
144158 {
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",
146188 "success": true
147189 }
148190 ],
149191 [
150192 "Parallel runs layers in parallel via parApply",
151193 {
152− "timestamp": "1771460758484.0",
194+ "timestamp": "1775221839085.0",
153195 "success": true
154196 }
155197 ],
156198 [
157199 "Scoped Layers combines scoped and non-scoped layers",
158200 {
159− "timestamp": "1771460758484.0",
201+ "timestamp": "1775221839085.0",
160202 "success": true
161203 }
162204 ],
163205 [
164206 "Scoped Layers fresh layer builds twice even when same base layer",
165207 {
166− "timestamp": "1771460758484.0",
208+ "timestamp": "1775221839085.0",
167209 "success": true
168210 }
169211 ],
170212 [
171213 "Scoped Layers runs finalizers in reverse order on success",
172214 {
173− "timestamp": "1771460758484.0",
215+ "timestamp": "1775221839085.0",
174216 "success": true
175217 }
176218 ],
177219 [
178220 "Scoped Layers runs finalizers when the callback throws",
179221 {
180− "timestamp": "1771460758484.0",
222+ "timestamp": "1775221839085.0",
181223 "success": true
182224 }
183225 ],
184226 [
185227 "Scoped Layers threads finalizers through vertical composition (provide)",
186228 {
187− "timestamp": "1771460758484.0",
229+ "timestamp": "1775221839085.0",
188230 "success": true
189231 }
190232 ],
191233 [
192234 "Scoped Layers works with bracketLayer",
193235 {
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",
195251 "success": true
196252 }
197253 ],
198254 [
199255 "wireLayers memoizes shared dependencies within wireLayers",
200256 {
201− "timestamp": "1771460758484.0",
257+ "timestamp": "1775221839085.0",
202258 "success": true
203259 }
204260 ],
205261 [
206262 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
207263 {
208− "timestamp": "1771460758484.0",
264+ "timestamp": "1775221839085.0",
209265 "success": true
210266 }
211267 ],
212268 [
213269 "wireLayers wires a diamond dependency",
214270 {
215− "timestamp": "1771460758484.0",
271+ "timestamp": "1775221839085.0",
216272 "success": true
217273 }
218274 ],
219275 [
220276 "wireLayers wires a simple linear chain (A -> B -> C)",
221277 {
222− "timestamp": "1771460758484.0",
278+ "timestamp": "1775221839085.0",
223279 "success": true
224280 }
225281 ],
226282 [
227283 "wireLayers wires independent layers with no inter-dependencies",
228284 {
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",
230293 "success": true
231294 }
232295 ]
README.md+1
@@ -240,3 +240,4 @@ combineRequirements (OmLayer build1) (OmLayer build2) = makeLayer do
240240
241241**Status**: Proof of concept complete ✓ with working runtime implementation
242242**Next**: Implement full instance-resolution based layer system with provideLayers API
243+
compile-fail-tests/RunLayerOpenRowMissingDep.pursnew file+19
@@ -0,0 +1,19 @@
1+-- EXPECT: Prim.RowList.RowToList
2+module Yoga.Om.Layer.CompileFailTest where
3+
4+import Prelude
5+
6+import Yoga.Om as Om
7+import Yoga.Om.Layer (OmLayer, makeLayer, runLayer)
8+
9+type Fastify = String
10+type Connection = String
11+
12+apiLayer :: forall r. OmLayer (fastify :: Fastify, sqlite :: Connection | r) () {}
13+apiLayer = makeLayer do
14+ { fastify, sqlite } <- Om.ask
15+ let _ = fastify <> sqlite
16+ pure {}
17+
18+bad :: Om.Om { fastify :: Fastify } () {}
19+bad = runLayer { fastify: "server" } apiLayer
spago.lock+12−6
@@ -6,7 +6,7 @@
66 "core": {
77 "dependencies": [
88 {
9− "aff": ">=7.0.0 <9.0.0"
9+ "aff": ">=8.0.0 <9.0.0"
1010 },
1111 {
1212 "aff-retry": ">=2.0.0 <3.0.0"
@@ -14,6 +14,9 @@
1414 {
1515 "arrays": ">=7.3.0 <8.0.0"
1616 },
17+ {
18+ "control": ">=6.0.0 <7.0.0"
19+ },
1720 {
1821 "effect": ">=4.0.0 <5.0.0"
1922 },
@@ -47,6 +50,9 @@
4750 {
4851 "refs": ">=6.0.0 <7.0.0"
4952 },
53+ {
54+ "tailrec": ">=6.1.0 <7.0.0"
55+ },
5056 {
5157 "transformers": ">=6.1.0 <7.0.0"
5258 },
@@ -63,7 +69,7 @@
6369 "variant": ">=8.0.0 <9.0.0"
6470 },
6571 {
66− "yoga-om": ">=1.4.0 <2.0.0"
72+ "yoga-om": ">=2.0.0 <3.0.0"
6773 }
6874 ]
6975 },
@@ -998,8 +1004,8 @@
9981004 },
9991005 "spec": {
10001006 "type": "registry",
1001− "version": "8.1.1",
1002− "integrity": "sha256-b6qjDb7lUJGLV+WEoywLu6BCXC4zGpfbN43mTL+vBeI=",
1007+ "version": "8.1.2",
1008+ "integrity": "sha256-klPPJ6lBQI1CGOAdLJ+ZoGRt8XZippkTKjn53BrSkL0=",
10031009 "dependencies": [
10041010 "aff",
10051011 "ansi",
@@ -1225,8 +1231,8 @@
12251231 },
12261232 "yoga-om": {
12271233 "type": "registry",
1228− "version": "1.5.2",
1229− "integrity": "sha256-2ys14BF6NPgzFDFUIFk0EDxmqkA8+yghIA9TXHlOmE0=",
1234+ "version": "2.0.0",
1235+ "integrity": "sha256-eenoujD2rJzR6gYJbbIiqLhXHC1C5dagu91HZkAEt/Y=",
12301236 "dependencies": [
12311237 "aff",
12321238 "aff-retry",
spago.yaml+4−2
@@ -2,7 +2,7 @@ workspace: {}
22package:
33 name: yoga-om-layer
44 publish:
5− version: 1.2.2
5+ version: 2.1.0
66 license: MIT
77 location:
88 githubOwner: rowtype-yoga
@@ -11,6 +11,7 @@ package:
1111 - aff: ">=8.0.0 <9.0.0"
1212 - aff-retry: ">=2.0.0 <3.0.0"
1313 - arrays: ">=7.3.0 <8.0.0"
14+ - control: ">=6.0.0 <7.0.0"
1415 - effect: ">=4.0.0 <5.0.0"
1516 - exceptions: ">=6.1.0 <7.0.0"
1617 - foldable-traversable: ">=6.0.0 <7.0.0"
@@ -22,12 +23,13 @@ package:
2223 - record: ">=4.0.0 <5.0.0"
2324 - record-studio: ">=1.0.4 <2.0.0"
2425 - refs: ">=6.0.0 <7.0.0"
26+ - tailrec: ">=6.1.0 <7.0.0"
2527 - transformers: ">=6.1.0 <7.0.0"
2628 - type-equality: ">=4.0.1 <5.0.0"
2729 - typelevel-prelude: ">=7.0.0 <8.0.0"
2830 - unsafe-coerce: ">=6.0.0 <7.0.0"
2931 - variant: ">=8.0.0 <9.0.0"
30− - yoga-om: ">=1.5.2 <2.0.0"
32+ - yoga-om: ">=2.0.0 <3.0.0"
3133 test:
3234 main: Test.Main
3335 dependencies:
src/Yoga/Om/Layer.purs+70−3
@@ -15,6 +15,7 @@ module Yoga.Om.Layer
1515 , withScopedWith
1616 , scoped
1717 , combineRequirements
18+ , withValue
1819 , provide
1920 , (>->)
2021 , recovering
@@ -52,10 +53,19 @@ module Yoga.Om.Layer
5253
5354import Prelude
5455
55−import Control.Monad.Error.Class (catchError, throwError)
56−import Data.Symbol (class IsSymbol)
56+import Control.Alt (class Alt)
57+import Control.Alt as Alt
58+import Control.Alternative (class Alternative)
59+import Control.Monad.Error.Class (class MonadError, class MonadThrow, catchError, throwError)
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)
5763import Control.Parallel (class Parallel)
5864import 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)
5969import Data.Array as Array
6070import Data.Foldable (for_)
6171import Data.Map (Map)
@@ -66,7 +76,10 @@ import Data.Variant (class VariantMatchCases, onMatch)
6676import Effect (Effect)
6777import Effect.Aff (Aff, bracket)
6878import Effect.Aff.Retry (RetryPolicyM, RetryStatus, applyAndDelay, defaultRetryStatus)
69−import Effect.Class (liftEffect)
79+import Effect.Aff.Class (class MonadAff)
80+import Effect.Aff.Class as Aff
81+import Effect.Class (class MonadEffect, liftEffect)
82+import Effect.Class (class MonadEffect, liftEffect) as Effect
7083import Effect.Exception (Error)
7184import Effect.Ref (Ref)
7285import Effect.Ref as Ref
@@ -86,6 +99,7 @@ import Type.Equality (class TypeEquals)
8699import Unsafe.Coerce (unsafeCoerce)
87100import Yoga.Om (Om, ParOm)
88101import Yoga.Om as Om
102+import Yoga.Om.Error (Exception)
89103
90104-- =============================================================================
91105-- Scope — a first-class representation of resource lifetime
@@ -174,6 +188,36 @@ instance Bind (OmLayer req err) where
174188
175189instance Monad (OmLayer req err)
176190
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+
177221instance Functor (ParOmLayer req err) where
178222 map f (ParOmLayer p) = ParOmLayer (map f p)
179223
@@ -183,6 +227,14 @@ instance Apply (ParOmLayer req err) where
183227instance Applicative (ParOmLayer req err) where
184228 pure a = ParOmLayer (pure a)
185229
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+
186238instance Parallel (ParOmLayer req err) (OmLayer req err) where
187239 parallel (OmLayer _ om) = ParOmLayer (Parallel.parallel om)
188240 sequential (ParOmLayer p) = OmLayer (freshId unit) (Parallel.sequential p)
@@ -340,9 +392,13 @@ else instance
340392-- =============================================================================
341393
342394-- | Run a layer with a given context, with custom error if requirements aren't met.
395+-- | `runLayer` requires a closed requirement row. If you have an open layer like
396+-- | `OmLayer (db :: Db | r) err a`, specialize it to a closed row before running
397+-- | it directly.
343398runLayer
344399 :: forall req err a available
345400 . CheckAllProvided req available
401+ => Keys req
346402 => Record available
347403 -> OmLayer req err a
348404 -> Om (Record available) err a
@@ -440,6 +496,17 @@ combineRequirements layer1 layer2 = OmLayer (freshId unit) do
440496 rec2 <- Om.expand (buildLayer layer2)
441497 pure (Record.merge rec1 rec2)
442498
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+
443510-- =============================================================================
444511-- Vertical composition — feed output of one layer into input of another
445512-- =============================================================================
test/Test/Yoga/Om/Layer/InstancesSpec.purs+93
@@ -2,11 +2,14 @@ module Test.Yoga.Om.Layer.InstancesSpec where
22
33import Prelude
44
5+import Control.Alt ((<|>))
6+import Control.Monad.Reader (ask, local)
57import Control.Parallel (parApply)
68import Effect.Class (liftEffect)
79import Effect.Ref as Ref
810import Test.Spec (Spec, describe, it)
911import Test.Spec.Assertions (shouldEqual)
12+import Effect.Exception as Exception
1013import Yoga.Om as Om
1114import Yoga.Om.Layer (OmLayer, makeLayer, runLayer)
1215
@@ -46,6 +49,96 @@ spec = do
4649 result <- Om.runOm {} { exception: \_ -> pure { result: 0 } } (runLayer {} layer)
4750 result.result `shouldEqual` 15
4851
52+ it "runs layers against a superset context" do
53+ let
54+ layer :: OmLayer (a :: Int) () { out :: Int }
55+ layer = makeLayer do
56+ { a } <- Om.ask
57+ pure { out: a + 1 }
58+
59+ ctx = { a: 41, extra: "ok" }
60+
61+ result <- Om.runOm ctx { exception: \_ -> pure { out: 0 } } (runLayer ctx layer)
62+ result.out `shouldEqual` 42
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+
49142 describe "Parallel" do
50143
51144 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"