git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
Add runScopedWith/withScopedWith and error channel tests for scoped layers
b070f43Mark
.psc-ide-port+1−1
@@ -1 +1 @@
1−15820
1+15887
src/Yoga/Om/Layer.purs+66−23
@@ -1,6 +1,7 @@
1-- @inline export runLayer arity=2
1-- @inline export runLayer arity=2
2module Yoga.Om.Layer
2module Yoga.Om.Layer
3 ( OmLayer
3 ( OmLayer
4+ , ParOmLayer
4 , Scope
5 , Scope
5 , makeLayer
6 , makeLayer
6 , makeScopedLayer
7 , makeScopedLayer
@@ -22,6 +23,8 @@ module Yoga.Om.Layer
22
23
23import Prelude
24import Prelude
24
25
26+import Control.Parallel (class Parallel)
27+import Control.Parallel as Parallel
25import Data.Array as Array
28import Data.Array as Array
26import Data.Foldable (for_)
29import Data.Foldable (for_)
27import Data.Map (Map)
30import Data.Map (Map)
@@ -43,7 +46,7 @@ import Record as Record
43import Record.Studio (class Keys)
46import Record.Studio (class Keys)
44import Type.Equality (class TypeEquals)
47import Type.Equality (class TypeEquals)
45import Unsafe.Coerce (unsafeCoerce)
48import Unsafe.Coerce (unsafeCoerce)
46−import Yoga.Om (Om)
49+import Yoga.Om (Om, ParOm)
47import Yoga.Om as Om
50import Yoga.Om as Om
48
51
49-- =============================================================================
52-- =============================================================================
@@ -102,9 +105,49 @@ freshId _ = unsafePerformEffect nextId
102-- OmLayer
105-- OmLayer
103-- =============================================================================
106-- =============================================================================
104
107
105−-- | A layer that requires dependencies (req) and provides services (prov).
108+-- | A layer that requires dependencies (req), may fail with (err),
109+-- | and produces a value (typically Record prov).
106-- | Each layer has a unique identity used for automatic memoization within a Scope.
110-- | Each layer has a unique identity used for automatic memoization within a Scope.
107−data OmLayer req prov err = OmLayer Int (Om (Record req) err (Record prov))
111+data OmLayer req err a = OmLayer Int (Om (Record req) err a)
112+
113+-- =============================================================================
114+-- ParOmLayer — parallel counterpart
115+-- =============================================================================
116+
117+data ParOmLayer req err a = ParOmLayer (ParOm (Record req) err a)
118+
119+-- =============================================================================
120+-- Instances
121+-- =============================================================================
122+
123+instance Functor (OmLayer req err) where
124+ map f layer = OmLayer (freshId unit) (map f (buildLayer layer))
125+
126+instance Apply (OmLayer req err) where
127+ apply f a = OmLayer (freshId unit) (apply (buildLayer f) (buildLayer a))
128+
129+instance Applicative (OmLayer req err) where
130+ pure a = OmLayer (freshId unit) (pure a)
131+
132+instance Bind (OmLayer req err) where
133+ bind layer f = OmLayer (freshId unit) do
134+ a <- buildLayer layer
135+ buildLayer (f a)
136+
137+instance Monad (OmLayer req err)
138+
139+instance Functor (ParOmLayer req err) where
140+ map f (ParOmLayer p) = ParOmLayer (map f p)
141+
142+instance Apply (ParOmLayer req err) where
143+ apply (ParOmLayer f) (ParOmLayer a) = ParOmLayer (apply f a)
144+
145+instance Applicative (ParOmLayer req err) where
146+ pure a = ParOmLayer (pure a)
147+
148+instance Parallel (ParOmLayer req err) (OmLayer req err) where
149+ parallel (OmLayer _ om) = ParOmLayer (Parallel.parallel om)
150+ sequential (ParOmLayer p) = OmLayer (freshId unit) (Parallel.sequential p)
108
151
109-- =============================================================================
152-- =============================================================================
110-- Automatic memoization
153-- Automatic memoization
@@ -113,7 +156,7 @@ data OmLayer req prov err = OmLayer Int (Om (Record req) err (Record prov))
113-- | Wrap an Om computation with cache logic.
156-- | Wrap an Om computation with cache logic.
114-- | If a scope is available in the context, checks/populates the cache by layer ID.
157-- | If a scope is available in the context, checks/populates the cache by layer ID.
115-- | If no scope is present, runs the computation directly.
158-- | If no scope is present, runs the computation directly.
116−withCache :: forall req prov err. Int -> Om (Record req) err (Record prov) -> Om (Record req) err (Record prov)
159+withCache :: forall req err a. Int -> Om (Record req) err a -> Om (Record req) err a
117withCache layerId om = do
160withCache layerId om = do
118 ctx <- Om.ask
161 ctx <- Om.ask
119 case toMaybe (tryGetScope ctx) of
162 case toMaybe (tryGetScope ctx) of
@@ -134,11 +177,11 @@ withCache layerId om = do
134-- | Create a layer from an Om computation.
177-- | Create a layer from an Om computation.
135-- | The layer is automatically memoized within a Scope — the same layer value
178-- | The layer is automatically memoized within a Scope — the same layer value
136-- | used in multiple branches of a dependency graph builds only once.
179-- | used in multiple branches of a dependency graph builds only once.
137−makeLayer :: forall req prov err. Om (Record req) err (Record prov) -> OmLayer req prov err
180+makeLayer :: forall req err a. Om (Record req) err a -> OmLayer req err a
138makeLayer om = OmLayer (freshId unit) om
181makeLayer om = OmLayer (freshId unit) om
139
182
140-- | Extract the Om from a layer, wrapped with cache logic.
183-- | Extract the Om from a layer, wrapped with cache logic.
141−buildLayer :: forall req prov err. OmLayer req prov err -> Om (Record req) err (Record prov)
184+buildLayer :: forall req err a. OmLayer req err a -> Om (Record req) err a
142buildLayer (OmLayer layerId om) = withCache layerId om
185buildLayer (OmLayer layerId om) = withCache layerId om
143
186
144-- | Create a scoped layer with acquire/release semantics.
187-- | Create a scoped layer with acquire/release semantics.
@@ -148,7 +191,7 @@ makeScopedLayer
148 :: forall req prov err
191 :: forall req prov err
149 . Om { scope :: Scope | req } err (Record prov)
192 . Om { scope :: Scope | req } err (Record prov)
150 -> (Record prov -> Aff Unit)
193 -> (Record prov -> Aff Unit)
151− -> OmLayer (scope :: Scope | req) prov err
194+ -> OmLayer (scope :: Scope | req) err (Record prov)
152makeScopedLayer acquire release = makeLayer do
195makeScopedLayer acquire release = makeLayer do
153 acquireRelease acquire release
196 acquireRelease acquire release
154
197
@@ -159,7 +202,7 @@ bracketLayer
159 . Om { scope :: Scope | req } err resource
202 . Om { scope :: Scope | req } err resource
160 -> (resource -> Aff Unit)
203 -> (resource -> Aff Unit)
161 -> (resource -> Om { scope :: Scope | req } err (Record prov))
204 -> (resource -> Om { scope :: Scope | req } err (Record prov))
162− -> OmLayer (scope :: Scope | req) prov err
205+ -> OmLayer (scope :: Scope | req) err (Record prov)
163bracketLayer acquire release use = makeLayer do
206bracketLayer acquire release use = makeLayer do
164 resource <- acquireRelease acquire (\r -> release r)
207 resource <- acquireRelease acquire (\r -> release r)
165 use resource
208 use resource
@@ -181,7 +224,7 @@ acquireRelease acquire release = do
181-- | Create a fresh (non-memoized) copy of a layer.
224-- | Create a fresh (non-memoized) copy of a layer.
182-- | The new layer has a unique identity and will always build independently,
225-- | The new layer has a unique identity and will always build independently,
183-- | even within the same scope.
226-- | even within the same scope.
184−fresh :: forall req prov err. OmLayer req prov err -> OmLayer req prov err
227+fresh :: forall req err a. OmLayer req err a -> OmLayer req err a
185fresh (OmLayer _ om) = OmLayer (freshId unit) om
228fresh (OmLayer _ om) = OmLayer (freshId unit) om
186
229
187-- =============================================================================
230-- =============================================================================
@@ -260,14 +303,14 @@ else instance
260
303
261-- | Run a layer with a given context, with custom error if requirements aren't met.
304-- | Run a layer with a given context, with custom error if requirements aren't met.
262runLayer
305runLayer
263− :: forall req prov err available
306+ :: forall req err a available
264 . CheckAllProvided req available
307 . CheckAllProvided req available
265 => Record available
308 => Record available
266− -> OmLayer req prov err
309+ -> OmLayer req err a
267− -> Om (Record available) err (Record prov)
310+ -> Om (Record available) err a
268runLayer _ctx layer = widenCtx (buildLayer layer)
311runLayer _ctx layer = widenCtx (buildLayer layer)
269 where
312 where
270− widenCtx :: Om (Record req) err (Record prov) -> Om (Record available) err (Record prov)
313+ widenCtx :: Om (Record req) err a -> Om (Record available) err a
271 widenCtx = unsafeCoerce
314 widenCtx = unsafeCoerce
272
315
273-- | Build a fully-provided scoped layer, return the provisions.
316-- | Build a fully-provided scoped layer, return the provisions.
@@ -275,7 +318,7 @@ runLayer _ctx layer = widenCtx (buildLayer layer)
275-- | For layers with typed errors, use `runScopedWith`.
318-- | For layers with typed errors, use `runScopedWith`.
276runScoped
319runScoped
277 :: forall prov
320 :: forall prov
278− . OmLayer (scope :: Scope) prov ()
321+ . OmLayer (scope :: Scope) () (Record prov)
279 -> Aff (Record prov)
322 -> Aff (Record prov)
280runScoped layer = withScoped layer pure
323runScoped layer = withScoped layer pure
281
324
@@ -286,7 +329,7 @@ runScoped layer = withScoped layer pure
286-- | For layers with typed errors, use `withScopedWith`.
329-- | For layers with typed errors, use `withScopedWith`.
287withScoped
330withScoped
288 :: forall prov a
331 :: forall prov a
289− . OmLayer (scope :: Scope) prov ()
332+ . OmLayer (scope :: Scope) () (Record prov)
290 -> (Record prov -> Aff a)
333 -> (Record prov -> Aff a)
291 -> Aff a
334 -> Aff a
292withScoped = withScopedWith { exception: \_ -> pure (unsafeCoerce {}) }
335withScoped = withScopedWith { exception: \_ -> pure (unsafeCoerce {}) }
@@ -298,7 +341,7 @@ runScopedWith
298 => VariantMatchCases rl err_ (Aff (Record prov))
341 => VariantMatchCases rl err_ (Aff (Record prov))
299 => Union err_ () (exception :: Error | err)
342 => Union err_ () (exception :: Error | err)
300 => { exception :: Error -> Aff (Record prov) | r }
343 => { exception :: Error -> Aff (Record prov) | r }
301− -> OmLayer (scope :: Scope) prov err
344+ -> OmLayer (scope :: Scope) err (Record prov)
302 -> Aff (Record prov)
345 -> Aff (Record prov)
303runScopedWith handlers layer = withScopedWith handlers layer pure
346runScopedWith handlers layer = withScopedWith handlers layer pure
304
347
@@ -309,7 +352,7 @@ withScopedWith
309 => VariantMatchCases rl err_ (Aff (Record prov))
352 => VariantMatchCases rl err_ (Aff (Record prov))
310 => Union err_ () (exception :: Error | err)
353 => Union err_ () (exception :: Error | err)
311 => { exception :: Error -> Aff (Record prov) | r }
354 => { exception :: Error -> Aff (Record prov) | r }
312− -> OmLayer (scope :: Scope) prov err
355+ -> OmLayer (scope :: Scope) err (Record prov)
313 -> (Record prov -> Aff a)
356 -> (Record prov -> Aff a)
314 -> Aff a
357 -> Aff a
315withScopedWith handlers layer callback = bracket acquire release use
358withScopedWith handlers layer callback = bracket acquire release use
@@ -340,9 +383,9 @@ combineRequirements
340 => Nub provMergedRaw provMerged
383 => Nub provMergedRaw provMerged
341 => Keys req1
384 => Keys req1
342 => Keys req2
385 => Keys req2
343− => OmLayer req1 prov1 err1
386+ => OmLayer req1 err1 (Record prov1)
344− -> OmLayer req2 prov2 err2
387+ -> OmLayer req2 err2 (Record prov2)
345− -> OmLayer reqDeduped provMerged errDeduped
388+ -> OmLayer reqDeduped errDeduped (Record provMerged)
346combineRequirements layer1 layer2 = OmLayer (freshId unit) do
389combineRequirements layer1 layer2 = OmLayer (freshId unit) do
347 rec1 <- Om.expand (buildLayer layer1)
390 rec1 <- Om.expand (buildLayer layer1)
348 rec2 <- Om.expand (buildLayer layer2)
391 rec2 <- Om.expand (buildLayer layer2)
@@ -366,9 +409,9 @@ provide
366 => Keys req
409 => Keys req
367 => Keys prov1
410 => Keys prov1
368 => Keys req2
411 => Keys req2
369− => OmLayer req2 prov2 err2
412+ => OmLayer req2 err2 (Record prov2)
370− -> OmLayer req prov1 err1
413+ -> OmLayer req err1 (Record prov1)
371− -> OmLayer req prov2 errDeduped
414+ -> OmLayer req errDeduped (Record prov2)
372provide l2 l1 = OmLayer (freshId unit) do
415provide l2 l1 = OmLayer (freshId unit) do
373 prov1 <- Om.expand (buildLayer l1)
416 prov1 <- Om.expand (buildLayer l1)
374 Om.expand (buildLayer l2) # Om.widenCtx prov1
417 Om.expand (buildLayer l2) # Om.widenCtx prov1
test/Playground.purs+4−4
@@ -24,22 +24,22 @@ type Cache = { get :: String -> Effect (Maybe String) }
24type CacheL r = (cache :: Cache | r)
24type CacheL r = (cache :: Cache | r)
25type CacheL_ = CacheL ()
25type CacheL_ = CacheL ()
26
26
27−loggerLayer :: OmLayer ConfigL_ LoggerL_ ()
27+loggerLayer :: OmLayer ConfigL_ () { logger :: Logger }
28loggerLayer = makeLayer do
28loggerLayer = makeLayer do
29 { config } <- Om.ask
29 { config } <- Om.ask
30 pure { logger: { log: \msg -> pure unit } }
30 pure { logger: { log: \msg -> pure unit } }
31
31
32−databaseLayer :: OmLayer ConfigL_ DatabaseL_ ()
32+databaseLayer :: OmLayer ConfigL_ () { database :: Database }
33databaseLayer = makeLayer do
33databaseLayer = makeLayer do
34 { config } <- Om.ask
34 { config } <- Om.ask
35 pure { database: { query: \q -> pure [] } }
35 pure { database: { query: \q -> pure [] } }
36
36
37−cacheLayer :: OmLayer (LoggerL + DatabaseL_) CacheL_ ()
37+cacheLayer :: OmLayer (LoggerL + DatabaseL_) () { cache :: Cache }
38cacheLayer = makeLayer do
38cacheLayer = makeLayer do
39 { logger, database } <- Om.ask
39 { logger, database } <- Om.ask
40 pure { cache: { get: \key -> pure Nothing } }
40 pure { cache: { get: \key -> pure Nothing } }
41
41
42−programLayer :: OmLayer (DatabaseL + CacheL_) () ()
42+programLayer :: OmLayer (DatabaseL + CacheL_) () {}
43programLayer = makeLayer do
43programLayer = makeLayer do
44 pure {}
44 pure {}
45
45
test/Test/Main.purs+102−45
@@ -2,6 +2,7 @@ module Test.Main where
2
2
3import Prelude
3import Prelude
4
4
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
29
30
30 it "proves Row.Nub deduplicates shared requirements at type level" do
31 it "proves Row.Nub deduplicates shared requirements at type level" do
31 let
32 let
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 } }
36
37
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 ""
51 let
52 let
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 []
81 let
82 let
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)
86
87
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)
91
92
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)
96
97
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
99
100
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 []
108 let
109 let
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 []
121 let
122 let
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)
126
127
127− upperLayer :: OmLayer (scope :: Scope, base :: String) (upper :: String) ()
128+ upperLayer :: OmLayer (scope :: Scope, base :: String) () { upper :: String }
128 upperLayer = makeScopedLayer
129 upperLayer = makeScopedLayer
129 ( do
130 ( do
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
160 let
161 let
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" }
165
166
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
168 where
169 where
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" }
173
174
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
176 where
177 where
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 []
192 let
193 let
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)
197
198
198− plainLayer :: OmLayer () (plain :: String) ()
199+ plainLayer :: OmLayer () () { plain :: String }
199 plainLayer = makeLayer (pure { plain: "no-finalizer" })
200 plainLayer = makeLayer (pure { plain: "no-finalizer" })
200
201
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
213 let
214 let
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" }
218
219
219− branch1 :: OmLayer (scope :: Scope) (out1 :: String) ()
220+ branch1 :: OmLayer (scope :: Scope) () { out1 :: String }
220 branch1 = consumer `provide` expensiveLayer
221 branch1 = consumer `provide` expensiveLayer
221 where
222 where
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" }
226
227
227− branch2 :: OmLayer (scope :: Scope) (out2 :: String) ()
228+ branch2 :: OmLayer (scope :: Scope) () { out2 :: String }
228 branch2 = consumer `provide` expensiveLayer
229 branch2 = consumer `provide` expensiveLayer
229 where
230 where
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 []
245 let
246 let
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)
250
251
251− repoBranch :: OmLayer (scope :: Scope) (repo :: String) ()
252+ repoBranch :: OmLayer (scope :: Scope) () { repo :: String }
252 repoBranch = repo `provide` dbLayer
253 repoBranch = repo `provide` dbLayer
253 where
254 where
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" }
258
259
259− analyticsBranch :: OmLayer (scope :: Scope) (analytics :: String) ()
260+ analyticsBranch :: OmLayer (scope :: Scope) () { analytics :: String }
260 analyticsBranch = analytics `provide` dbLayer
261 analyticsBranch = analytics `provide` dbLayer
261 where
262 where
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 []
277 let
278 let
278− connLayer :: OmLayer (scope :: Scope) (conn :: Int) ()
279+ connLayer :: OmLayer (scope :: Scope) () { conn :: Int }
279 connLayer = bracketLayer
280 connLayer = bracketLayer
280 ( do
281 ( do
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 })
286
287
287− branch1 :: OmLayer (scope :: Scope) (x :: String) ()
288+ branch1 :: OmLayer (scope :: Scope) () { x :: String }
288 branch1 = upper `provide` connLayer
289 branch1 = upper `provide` connLayer
289 where
290 where
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 }
294
295
295− branch2 :: OmLayer (scope :: Scope) (y :: String) ()
296+ branch2 :: OmLayer (scope :: Scope) () { y :: String }
296 branch2 = upper `provide` connLayer
297 branch2 = upper `provide` connLayer
297 where
298 where
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
312
313
313 it "composes layers with different error types" do
314 it "composes layers with different error types" do
314 let
315 let
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" }
318
319
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" }
322
323
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
325
326
326 result <- Om.runOm {}
327 result <- Om.runOm {}
@@ -334,11 +335,11 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
334
335
335 it "propagates errors from the first layer" do
336 it "propagates errors from the first layer" do
336 let
337 let
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" }
340
341
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" }
344
345
@@ -358,11 +359,11 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
358
359
359 it "propagates errors from the second layer" do
360 it "propagates errors from the second layer" do
360 let
361 let
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" }
364
365
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" }
368
369
@@ -382,11 +383,11 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
382
383
383 it "propagates errors through vertical composition" do
384 it "propagates errors through vertical composition" do
384 let
385 let
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" }
388
389
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 []
411 let
412 let
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)
416
417
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 []
438 let
439 let
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)
443
444
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" ]
463+
464+ describe "Functor / Apply / Bind" do
465+
466+ it "maps over layer output" do
467+ let
468+ layer :: OmLayer () () { value :: Int }
469+ layer = makeLayer (pure { value: 10 })
470+
471+ mapped = layer <#> \r -> { doubled: r.value * 2 }
472+
473+ result <- Om.runOm {} { exception: \_ -> pure { doubled: 0 } } (runLayer {} mapped)
474+ result.doubled `shouldEqual` 20
475+
476+ it "applies two layers" do
477+ let
478+ fLayer :: OmLayer () () ({ a :: Int } -> { a :: Int, b :: String })
479+ fLayer = makeLayer (pure \r -> { a: r.a, b: show r.a })
480+
481+ aLayer :: OmLayer () () { a :: Int }
482+ aLayer = makeLayer (pure { a: 42 })
483+
484+ result <- Om.runOm {} { exception: \_ -> pure { a: 0, b: "" } } (runLayer {} (fLayer <*> aLayer))
485+ result.a `shouldEqual` 42
486+ result.b `shouldEqual` "42"
487+
488+ it "binds layers sequentially" do
489+ let
490+ layer = do
491+ r <- makeLayer (pure { x: 5 }) :: OmLayer () () { x :: Int }
492+ makeLayer (pure { result: r.x + 10 })
493+
494+ result <- Om.runOm {} { exception: \_ -> pure { result: 0 } } (runLayer {} layer)
495+ result.result `shouldEqual` 15
496+
497+ describe "Parallel" do
498+
499+ it "runs layers in parallel via parApply" do
500+ log <- liftEffect $ Ref.new []
501+ let
502+ layerA :: OmLayer () () { a :: String }
503+ layerA = makeLayer do
504+ liftEffect $ Ref.modify_ (_ <> [ "a" ]) log
505+ pure { a: "A" }
506+
507+ layerB :: OmLayer () () { b :: String }
508+ layerB = makeLayer do
509+ liftEffect $ Ref.modify_ (_ <> [ "b" ]) log
510+ pure { b: "B" }
511+
512+ combined = parApply (map (\ra rb -> { a: ra.a, b: rb.b }) layerA) layerB
513+
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" ]