@@ -2,6 +2,7 @@ module Test.Main where
3import Prelude
3import Prelude
5+import Control.Parallel (parApply)
5import Effect (Effect)
6import Effect (Effect)
6import Effect.Aff (launchAff_, throwError, try)
7import Effect.Aff (launchAff_, throwError, try)
7import Effect.Aff.Class (liftAff)
8import Effect.Aff.Class (liftAff)
@@ -29,12 +30,12 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
30 it "proves Row.Nub deduplicates shared requirements at type level" do
31 it "proves Row.Nub deduplicates shared requirements at type level" do
32− loggerLayer :: OmLayer (config :: Config) (logger :: Logger) ()
33+ loggerLayer :: OmLayer (config :: Config) () { logger :: Logger }
33 loggerLayer = makeLayer do
34 loggerLayer = makeLayer do
34 { config } <- Om.ask
35 { config } <- Om.ask
35 pure { logger: { log: \msg -> Console.log $ "[" <> config.host <> "] " <> msg } }
36 pure { logger: { log: \msg -> Console.log $ "[" <> config.host <> "] " <> msg } }
37− databaseLayer :: OmLayer (config :: Config) (database :: Database) ()
38+ databaseLayer :: OmLayer (config :: Config) () { database :: Database }
38 databaseLayer = makeLayer do
39 databaseLayer = makeLayer do
39 { config } <- Om.ask
40 { config } <- Om.ask
40 pure { database: { query: \q -> pure [ "Result from " <> config.host ] } }
41 pure { database: { query: \q -> pure [ "Result from " <> config.host ] } }
@@ -49,7 +50,7 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
49 it "proves deduplication at runtime via shared Ref" do
50 it "proves deduplication at runtime via shared Ref" do
50 accessLog <- liftEffect $ Ref.new ""
51 accessLog <- liftEffect $ Ref.new ""
52− combinedLayer :: OmLayer (config :: Config, accessLog :: Ref.Ref String) (logger :: Logger, database :: Database) ()
53+ combinedLayer :: OmLayer (config :: Config, accessLog :: Ref.Ref String) () { logger :: Logger, database :: Database }
53 combinedLayer = makeLayer do
54 combinedLayer = makeLayer do
54 { config, accessLog: log } <- Om.ask
55 { config, accessLog: log } <- Om.ask
55 liftEffect $ Ref.modify_ (_ <> "config-accessed-by-logger ") log
56 liftEffect $ Ref.modify_ (_ <> "config-accessed-by-logger ") log
@@ -79,22 +80,22 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
79 it "runs finalizers in reverse order on success" do
80 it "runs finalizers in reverse order on success" do
80 log <- liftEffect $ Ref.new []
81 log <- liftEffect $ Ref.new []
82− layerA :: OmLayer (scope :: Scope) (a :: String) ()
83+ layerA :: OmLayer (scope :: Scope) () { a :: String }
83 layerA = makeScopedLayer
84 layerA = makeScopedLayer
84 (pure { a: "A" })
85 (pure { a: "A" })
85 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-A" ]) log)
86 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-A" ]) log)
87− layerB :: OmLayer (scope :: Scope) (b :: String) ()
88+ layerB :: OmLayer (scope :: Scope) () { b :: String }
88 layerB = makeScopedLayer
89 layerB = makeScopedLayer
89 (pure { b: "B" })
90 (pure { b: "B" })
90 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-B" ]) log)
91 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-B" ]) log)
92− layerC :: OmLayer (scope :: Scope) (c :: String) ()
93+ layerC :: OmLayer (scope :: Scope) () { c :: String }
93 layerC = makeScopedLayer
94 layerC = makeScopedLayer
94 (pure { c: "C" })
95 (pure { c: "C" })
95 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-C" ]) log)
96 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-C" ]) log)
97− layerBC :: OmLayer (scope :: Scope) (b :: String, c :: String) ()
98+ layerBC :: OmLayer (scope :: Scope) () { b :: String, c :: String }
98 layerBC = combineRequirements layerB layerC
99 layerBC = combineRequirements layerB layerC
100 combined = combineRequirements layerA layerBC
101 combined = combineRequirements layerA layerBC
@@ -106,7 +107,7 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
106 it "runs finalizers when the callback throws" do
107 it "runs finalizers when the callback throws" do
107 log <- liftEffect $ Ref.new []
108 log <- liftEffect $ Ref.new []
109− layer :: OmLayer (scope :: Scope) (value :: String) ()
110+ layer :: OmLayer (scope :: Scope) () { value :: String }
110 layer = makeScopedLayer
111 layer = makeScopedLayer
111 (pure { value: "acquired" })
112 (pure { value: "acquired" })
112 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "released" ]) log)
113 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "released" ]) log)
@@ -119,12 +120,12 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
119 it "threads finalizers through vertical composition (provide)" do
120 it "threads finalizers through vertical composition (provide)" do
120 log <- liftEffect $ Ref.new []
121 log <- liftEffect $ Ref.new []
122− baseLayer :: OmLayer (scope :: Scope) (base :: String) ()
123+ baseLayer :: OmLayer (scope :: Scope) () { base :: String }
123 baseLayer = makeScopedLayer
124 baseLayer = makeScopedLayer
124 (pure { base: "base-value" })
125 (pure { base: "base-value" })
125 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-base" ]) log)
126 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-base" ]) log)
127− upperLayer :: OmLayer (scope :: Scope, base :: String) (upper :: String) ()
128+ upperLayer :: OmLayer (scope :: Scope, base :: String) () { upper :: String }
128 upperLayer = makeScopedLayer
129 upperLayer = makeScopedLayer
130 { base } <- Om.ask
131 { base } <- Om.ask
@@ -158,23 +159,23 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
158 it "fresh layer builds twice even when same base layer" do
159 it "fresh layer builds twice even when same base layer" do
159 counter <- liftEffect $ Ref.new 0
160 counter <- liftEffect $ Ref.new 0
161− baseLayer :: OmLayer (scope :: Scope) (value :: String) ()
162+ baseLayer :: OmLayer (scope :: Scope) () { value :: String }
162 baseLayer = makeLayer do
163 baseLayer = makeLayer do
163 liftEffect $ Ref.modify_ (_ + 1) counter
164 liftEffect $ Ref.modify_ (_ + 1) counter
164 pure { value: "built" }
165 pure { value: "built" }
166− branch1 :: OmLayer (scope :: Scope) (out1 :: String) ()
167+ branch1 :: OmLayer (scope :: Scope) () { out1 :: String }
167 branch1 = consumer `provide` fresh baseLayer
168 branch1 = consumer `provide` fresh baseLayer
169− consumer :: OmLayer (scope :: Scope, value :: String) (out1 :: String) ()
170+ consumer :: OmLayer (scope :: Scope, value :: String) () { out1 :: String }
170 consumer = makeLayer do
171 consumer = makeLayer do
171 { value } <- Om.ask
172 { value } <- Om.ask
172 pure { out1: value <> "-1" }
173 pure { out1: value <> "-1" }
174− branch2 :: OmLayer (scope :: Scope) (out2 :: String) ()
175+ branch2 :: OmLayer (scope :: Scope) () { out2 :: String }
175 branch2 = consumer `provide` fresh baseLayer
176 branch2 = consumer `provide` fresh baseLayer
177− consumer :: OmLayer (scope :: Scope, value :: String) (out2 :: String) ()
178+ consumer :: OmLayer (scope :: Scope, value :: String) () { out2 :: String }
178 consumer = makeLayer do
179 consumer = makeLayer do
179 { value } <- Om.ask
180 { value } <- Om.ask
180 pure { out2: value <> "-2" }
181 pure { out2: value <> "-2" }
@@ -190,12 +191,12 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
190 it "combines scoped and non-scoped layers" do
191 it "combines scoped and non-scoped layers" do
191 log <- liftEffect $ Ref.new []
192 log <- liftEffect $ Ref.new []
193− scopedLayer :: OmLayer (scope :: Scope) (scoped :: String) ()
194+ scopedLayer :: OmLayer (scope :: Scope) () { scoped :: String }
194 scopedLayer = makeScopedLayer
195 scopedLayer = makeScopedLayer
195 (pure { scoped: "yes" })
196 (pure { scoped: "yes" })
196 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-scoped" ]) log)
197 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release-scoped" ]) log)
198− plainLayer :: OmLayer () (plain :: String) ()
199+ plainLayer :: OmLayer () () { plain :: String }
199 plainLayer = makeLayer (pure { plain: "no-finalizer" })
200 plainLayer = makeLayer (pure { plain: "no-finalizer" })
201 combined = combineRequirements scopedLayer plainLayer
202 combined = combineRequirements scopedLayer plainLayer
@@ -211,23 +212,23 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
211 it "builds the same layer only once when used in two branches" do
212 it "builds the same layer only once when used in two branches" do
212 counter <- liftEffect $ Ref.new 0
213 counter <- liftEffect $ Ref.new 0
214− expensiveLayer :: OmLayer (scope :: Scope) (value :: String) ()
215+ expensiveLayer :: OmLayer (scope :: Scope) () { value :: String }
215 expensiveLayer = makeLayer do
216 expensiveLayer = makeLayer do
216 liftEffect $ Ref.modify_ (_ + 1) counter
217 liftEffect $ Ref.modify_ (_ + 1) counter
217 pure { value: "built" }
218 pure { value: "built" }
219− branch1 :: OmLayer (scope :: Scope) (out1 :: String) ()
220+ branch1 :: OmLayer (scope :: Scope) () { out1 :: String }
220 branch1 = consumer `provide` expensiveLayer
221 branch1 = consumer `provide` expensiveLayer
222− consumer :: OmLayer (scope :: Scope, value :: String) (out1 :: String) ()
223+ consumer :: OmLayer (scope :: Scope, value :: String) () { out1 :: String }
223 consumer = makeLayer do
224 consumer = makeLayer do
224 { value } <- Om.ask
225 { value } <- Om.ask
225 pure { out1: value <> "-1" }
226 pure { out1: value <> "-1" }
227− branch2 :: OmLayer (scope :: Scope) (out2 :: String) ()
228+ branch2 :: OmLayer (scope :: Scope) () { out2 :: String }
228 branch2 = consumer `provide` expensiveLayer
229 branch2 = consumer `provide` expensiveLayer
230− consumer :: OmLayer (scope :: Scope, value :: String) (out2 :: String) ()
231+ consumer :: OmLayer (scope :: Scope, value :: String) () { out2 :: String }
231 consumer = makeLayer do
232 consumer = makeLayer do
232 { value } <- Om.ask
233 { value } <- Om.ask
233 pure { out2: value <> "-2" }
234 pure { out2: value <> "-2" }
@@ -243,23 +244,23 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
243 it "memoized scoped layer runs finalizer only once" do
244 it "memoized scoped layer runs finalizer only once" do
244 log <- liftEffect $ Ref.new []
245 log <- liftEffect $ Ref.new []
246− dbLayer :: OmLayer (scope :: Scope) (db :: String) ()
247+ dbLayer :: OmLayer (scope :: Scope) () { db :: String }
247 dbLayer = makeScopedLayer
248 dbLayer = makeScopedLayer
248 (pure { db: "connected" })
249 (pure { db: "connected" })
249 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "db-closed" ]) log)
250 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "db-closed" ]) log)
251− repoBranch :: OmLayer (scope :: Scope) (repo :: String) ()
252+ repoBranch :: OmLayer (scope :: Scope) () { repo :: String }
252 repoBranch = repo `provide` dbLayer
253 repoBranch = repo `provide` dbLayer
254− repo :: OmLayer (scope :: Scope, db :: String) (repo :: String) ()
255+ repo :: OmLayer (scope :: Scope, db :: String) () { repo :: String }
255 repo = makeLayer do
256 repo = makeLayer do
256 { db } <- Om.ask
257 { db } <- Om.ask
257 pure { repo: db <> "-repo" }
258 pure { repo: db <> "-repo" }
259− analyticsBranch :: OmLayer (scope :: Scope) (analytics :: String) ()
260+ analyticsBranch :: OmLayer (scope :: Scope) () { analytics :: String }
260 analyticsBranch = analytics `provide` dbLayer
261 analyticsBranch = analytics `provide` dbLayer
262− analytics :: OmLayer (scope :: Scope, db :: String) (analytics :: String) ()
263+ analytics :: OmLayer (scope :: Scope, db :: String) () { analytics :: String }
263 analytics = makeLayer do
264 analytics = makeLayer do
264 { db } <- Om.ask
265 { db } <- Om.ask
265 pure { analytics: db <> "-analytics" }
266 pure { analytics: db <> "-analytics" }
@@ -275,7 +276,7 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
275 it "memoized bracketLayer acquires and releases only once" do
276 it "memoized bracketLayer acquires and releases only once" do
276 log <- liftEffect $ Ref.new []
277 log <- liftEffect $ Ref.new []
278− connLayer :: OmLayer (scope :: Scope) (conn :: Int) ()
279+ connLayer :: OmLayer (scope :: Scope) () { conn :: Int }
279 connLayer = bracketLayer
280 connLayer = bracketLayer
281 liftEffect $ Ref.modify_ (_ <> [ "acquire" ]) log
282 liftEffect $ Ref.modify_ (_ <> [ "acquire" ]) log
@@ -284,18 +285,18 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
284 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release" ]) log)
285 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release" ]) log)
285 (\n -> pure { conn: n })
286 (\n -> pure { conn: n })
287− branch1 :: OmLayer (scope :: Scope) (x :: String) ()
288+ branch1 :: OmLayer (scope :: Scope) () { x :: String }
288 branch1 = upper `provide` connLayer
289 branch1 = upper `provide` connLayer
290− upper :: OmLayer (scope :: Scope, conn :: Int) (x :: String) ()
291+ upper :: OmLayer (scope :: Scope, conn :: Int) () { x :: String }
291 upper = makeLayer do
292 upper = makeLayer do
292 { conn } <- Om.ask
293 { conn } <- Om.ask
293 pure { x: show conn }
294 pure { x: show conn }
295− branch2 :: OmLayer (scope :: Scope) (y :: String) ()
296+ branch2 :: OmLayer (scope :: Scope) () { y :: String }
296 branch2 = upper `provide` connLayer
297 branch2 = upper `provide` connLayer
298− upper :: OmLayer (scope :: Scope, conn :: Int) (y :: String) ()
299+ upper :: OmLayer (scope :: Scope, conn :: Int) () { y :: String }
299 upper = makeLayer do
300 upper = makeLayer do
300 { conn } <- Om.ask
301 { conn } <- Om.ask
301 pure { y: show (conn + 1) }
302 pure { y: show (conn + 1) }
@@ -312,15 +313,15 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
313 it "composes layers with different error types" do
314 it "composes layers with different error types" do
315− dbLayer :: OmLayer () (db :: String) (dbError :: String)
316+ dbLayer :: OmLayer () (dbError :: String) { db :: String }
316 dbLayer = makeLayer do
317 dbLayer = makeLayer do
317 pure { db: "connected" }
318 pure { db: "connected" }
319− cacheLayer :: OmLayer () (cache :: String) (cacheError :: String)
320+ cacheLayer :: OmLayer () (cacheError :: String) { cache :: String }
320 cacheLayer = makeLayer do
321 cacheLayer = makeLayer do
321 pure { cache: "ready" }
322 pure { cache: "ready" }
323− combined :: OmLayer () (db :: String, cache :: String) (dbError :: String, cacheError :: String)
324+ combined :: OmLayer () (dbError :: String, cacheError :: String) { db :: String, cache :: String }
324 combined = combineRequirements dbLayer cacheLayer
325 combined = combineRequirements dbLayer cacheLayer
326 result <- Om.runOm {}
327 result <- Om.runOm {}
@@ -334,11 +335,11 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
335 it "propagates errors from the first layer" do
336 it "propagates errors from the first layer" do
337− failingLayer :: OmLayer () (db :: String) (dbError :: String)
338+ failingLayer :: OmLayer () (dbError :: String) { db :: String }
338 failingLayer = makeLayer do
339 failingLayer = makeLayer do
339 Om.throw { dbError: "connection refused" }
340 Om.throw { dbError: "connection refused" }
341− okLayer :: OmLayer () (cache :: String) ()
342+ okLayer :: OmLayer () () { cache :: String }
342 okLayer = makeLayer do
343 okLayer = makeLayer do
343 pure { cache: "ready" }
344 pure { cache: "ready" }
@@ -358,11 +359,11 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
359 it "propagates errors from the second layer" do
360 it "propagates errors from the second layer" do
361− okLayer :: OmLayer () (db :: String) ()
362+ okLayer :: OmLayer () () { db :: String }
362 okLayer = makeLayer do
363 okLayer = makeLayer do
363 pure { db: "connected" }
364 pure { db: "connected" }
365− failingLayer :: OmLayer () (cache :: String) (cacheError :: String)
366+ failingLayer :: OmLayer () (cacheError :: String) { cache :: String }
366 failingLayer = makeLayer do
367 failingLayer = makeLayer do
367 Om.throw { cacheError: "cache unavailable" }
368 Om.throw { cacheError: "cache unavailable" }
@@ -382,11 +383,11 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
383 it "propagates errors through vertical composition" do
384 it "propagates errors through vertical composition" do
385− baseLayer :: OmLayer () (base :: String) (baseError :: String)
386+ baseLayer :: OmLayer () (baseError :: String) { base :: String }
386 baseLayer = makeLayer do
387 baseLayer = makeLayer do
387 Om.throw { baseError: "base failed" }
388 Om.throw { baseError: "base failed" }
389− upperLayer :: OmLayer (base :: String) (upper :: String) (upperError :: String)
390+ upperLayer :: OmLayer (base :: String) (upperError :: String) { upper :: String }
390 upperLayer = makeLayer do
391 upperLayer = makeLayer do
391 { base } <- Om.ask
392 { base } <- Om.ask
392 pure { upper: base <> "-extended" }
393 pure { upper: base <> "-extended" }
@@ -409,12 +410,12 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
409 it "scoped layers with errors run via runScopedWith" do
410 it "scoped layers with errors run via runScopedWith" do
410 log <- liftEffect $ Ref.new []
411 log <- liftEffect $ Ref.new []
412− dbLayer :: OmLayer (scope :: Scope) (db :: String) (dbError :: String)
413+ dbLayer :: OmLayer (scope :: Scope) (dbError :: String) { db :: String }
413 dbLayer = makeScopedLayer
414 dbLayer = makeScopedLayer
414 (pure { db: "connected" })
415 (pure { db: "connected" })
415 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "db-closed" ]) log)
416 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "db-closed" ]) log)
417− cacheLayer :: OmLayer (scope :: Scope) (cache :: String) (cacheError :: String)
418+ cacheLayer :: OmLayer (scope :: Scope) (cacheError :: String) { cache :: String }
418 cacheLayer = makeScopedLayer
419 cacheLayer = makeScopedLayer
419 (pure { cache: "ready" })
420 (pure { cache: "ready" })
420 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "cache-closed" ]) log)
421 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "cache-closed" ]) log)
@@ -436,12 +437,12 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
436 it "scoped layer error triggers finalizers" do
437 it "scoped layer error triggers finalizers" do
437 log <- liftEffect $ Ref.new []
438 log <- liftEffect $ Ref.new []
439− goodLayer :: OmLayer (scope :: Scope) (good :: String) ()
440+ goodLayer :: OmLayer (scope :: Scope) () { good :: String }
440 goodLayer = makeScopedLayer
441 goodLayer = makeScopedLayer
441 (pure { good: "ok" })
442 (pure { good: "ok" })
442 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "good-closed" ]) log)
443 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "good-closed" ]) log)
444− failLayer :: OmLayer (scope :: Scope) (bad :: String) (buildError :: String)
445+ failLayer :: OmLayer (scope :: Scope) (buildError :: String) { bad :: String }
445 failLayer = makeScopedLayer
446 failLayer = makeScopedLayer
446 (Om.throw { buildError: "build failed" })
447 (Om.throw { buildError: "build failed" })
447 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "bad-closed" ]) log)
448 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "bad-closed" ]) log)
@@ -459,3 +460,59 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
459 Left _ -> pure unit
460 Left _ -> pure unit
460 finalLog <- liftEffect $ Ref.read log
461 finalLog <- liftEffect $ Ref.read log
461 finalLog `shouldEqual` [ "good-closed" ]
462 finalLog `shouldEqual` [ "good-closed" ]
464+ describe "Functor / Apply / Bind" do
466+ it "maps over layer output" do
468+ layer :: OmLayer () () { value :: Int }
469+ layer = makeLayer (pure { value: 10 })
471+ mapped = layer <#> \r -> { doubled: r.value * 2 }
473+ result <- Om.runOm {} { exception: \_ -> pure { doubled: 0 } } (runLayer {} mapped)
474+ result.doubled `shouldEqual` 20
476+ it "applies two layers" do
478+ fLayer :: OmLayer () () ({ a :: Int } -> { a :: Int, b :: String })
479+ fLayer = makeLayer (pure \r -> { a: r.a, b: show r.a })
481+ aLayer :: OmLayer () () { a :: Int }
482+ aLayer = makeLayer (pure { a: 42 })
484+ result <- Om.runOm {} { exception: \_ -> pure { a: 0, b: "" } } (runLayer {} (fLayer <*> aLayer))
485+ result.a `shouldEqual` 42
486+ result.b `shouldEqual` "42"
488+ it "binds layers sequentially" do
491+ r <- makeLayer (pure { x: 5 }) :: OmLayer () () { x :: Int }
492+ makeLayer (pure { result: r.x + 10 })
494+ result <- Om.runOm {} { exception: \_ -> pure { result: 0 } } (runLayer {} layer)
495+ result.result `shouldEqual` 15
497+ describe "Parallel" do
499+ it "runs layers in parallel via parApply" do
500+ log <- liftEffect $ Ref.new []
502+ layerA :: OmLayer () () { a :: String }
503+ layerA = makeLayer do
504+ liftEffect $ Ref.modify_ (_ <> [ "a" ]) log
507+ layerB :: OmLayer () () { b :: String }
508+ layerB = makeLayer do
509+ liftEffect $ Ref.modify_ (_ <> [ "b" ]) log
512+ combined = parApply (map (\ra rb -> { a: ra.a, b: rb.b }) layerA) layerB
514+ result <- Om.runOm {} { exception: \_ -> pure { a: "", b: "" } } (runLayer {} combined)
515+ result.a `shouldEqual` "A"
516+ result.b `shouldEqual` "B"
517+ finalLog <- liftEffect $ Ref.read log
518+ finalLog `shouldEqual` [ "a", "b" ]