git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
commit1cd78101873a59aa794768d2ddec2a9890dc1d9a authorMark <mark.eibes@gmail.com> dateSat 14 Feb 2026, 09:26 (8 months ago) parent00a73db
2 files changed+131−56
.spec-results+33−33 src/Yoga/Om/Layer.purs+98−23
.spec-results+33−33
@@ -2,231 +2,231 @@
22 [
33 "Automatic Memoization builds the same layer only once when used in two branches",
44 {
5− "timestamp": "1771057450178.0",
5+ "timestamp": "1771057579324.0",
66 "success": true
77 }
88 ],
99 [
1010 "Automatic Memoization memoized bracketLayer acquires and releases only once",
1111 {
12− "timestamp": "1771057450178.0",
12+ "timestamp": "1771057579324.0",
1313 "success": true
1414 }
1515 ],
1616 [
1717 "Automatic Memoization memoized scoped layer runs finalizer only once",
1818 {
19− "timestamp": "1771057450178.0",
19+ "timestamp": "1771057579324.0",
2020 "success": true
2121 }
2222 ],
2323 [
2424 "Composition proves Row.Nub deduplicates shared requirements at type level",
2525 {
26− "timestamp": "1771057450178.0",
26+ "timestamp": "1771057579324.0",
2727 "success": true
2828 }
2929 ],
3030 [
3131 "Composition proves deduplication at runtime via shared Ref",
3232 {
33− "timestamp": "1771057450178.0",
33+ "timestamp": "1771057579324.0",
3434 "success": true
3535 }
3636 ],
3737 [
3838 "Error Channel composes layers with different error types",
3939 {
40− "timestamp": "1771057450178.0",
40+ "timestamp": "1771057579324.0",
4141 "success": true
4242 }
4343 ],
4444 [
4545 "Error Channel propagates errors from the first layer",
4646 {
47− "timestamp": "1771057450178.0",
47+ "timestamp": "1771057579324.0",
4848 "success": true
4949 }
5050 ],
5151 [
5252 "Error Channel propagates errors from the second layer",
5353 {
54− "timestamp": "1771057450178.0",
54+ "timestamp": "1771057579324.0",
5555 "success": true
5656 }
5757 ],
5858 [
5959 "Error Channel propagates errors through vertical composition",
6060 {
61− "timestamp": "1771057450178.0",
61+ "timestamp": "1771057579324.0",
6262 "success": true
6363 }
6464 ],
6565 [
6666 "Error Channel scoped layer error triggers finalizers",
6767 {
68− "timestamp": "1771057450178.0",
68+ "timestamp": "1771057579324.0",
6969 "success": true
7070 }
7171 ],
7272 [
7373 "Error Channel scoped layers with errors run via runScopedWith",
7474 {
75− "timestamp": "1771057450178.0",
75+ "timestamp": "1771057579324.0",
7676 "success": true
7777 }
7878 ],
7979 [
8080 "Functor / Apply / Bind applies two layers",
8181 {
82− "timestamp": "1771057450178.0",
82+ "timestamp": "1771057579324.0",
8383 "success": true
8484 }
8585 ],
8686 [
8787 "Functor / Apply / Bind binds layers sequentially",
8888 {
89− "timestamp": "1771057450178.0",
89+ "timestamp": "1771057579324.0",
9090 "success": true
9191 }
9292 ],
9393 [
9494 "Functor / Apply / Bind maps over layer output",
9595 {
96− "timestamp": "1771057450178.0",
96+ "timestamp": "1771057579324.0",
9797 "success": true
9898 }
9999 ],
100100 [
101101 "Layer recovering does not retry on non-matching errors",
102102 {
103− "timestamp": "1771057450178.0",
103+ "timestamp": "1771057579324.0",
104104 "success": true
105105 }
106106 ],
107107 [
108108 "Layer recovering only retries the specified error in a multi-error layer",
109109 {
110− "timestamp": "1771057450178.0",
110+ "timestamp": "1771057579324.0",
111111 "success": true
112112 }
113113 ],
114114 [
115115 "Layer recovering preserves the error when retries are exhausted",
116116 {
117− "timestamp": "1771057450178.0",
117+ "timestamp": "1771057579324.0",
118118 "success": true
119119 }
120120 ],
121121 [
122122 "Layer recovering retries layer construction on matching errors",
123123 {
124− "timestamp": "1771057450178.0",
124+ "timestamp": "1771057579324.0",
125125 "success": true
126126 }
127127 ],
128128 [
129129 "Layer recovering uses RetryStatus in the check",
130130 {
131− "timestamp": "1771057450178.0",
131+ "timestamp": "1771057579324.0",
132132 "success": true
133133 }
134134 ],
135135 [
136136 "Layer repeating repeats layer construction while condition holds",
137137 {
138− "timestamp": "1771057450178.0",
138+ "timestamp": "1771057579324.0",
139139 "success": true
140140 }
141141 ],
142142 [
143143 "Layer repeating stops when policy exhausted",
144144 {
145− "timestamp": "1771057450178.0",
145+ "timestamp": "1771057579324.0",
146146 "success": true
147147 }
148148 ],
149149 [
150150 "Parallel runs layers in parallel via parApply",
151151 {
152− "timestamp": "1771057450178.0",
152+ "timestamp": "1771057579324.0",
153153 "success": true
154154 }
155155 ],
156156 [
157157 "Scoped Layers combines scoped and non-scoped layers",
158158 {
159− "timestamp": "1771057450178.0",
159+ "timestamp": "1771057579324.0",
160160 "success": true
161161 }
162162 ],
163163 [
164164 "Scoped Layers fresh layer builds twice even when same base layer",
165165 {
166− "timestamp": "1771057450178.0",
166+ "timestamp": "1771057579324.0",
167167 "success": true
168168 }
169169 ],
170170 [
171171 "Scoped Layers runs finalizers in reverse order on success",
172172 {
173− "timestamp": "1771057450178.0",
173+ "timestamp": "1771057579324.0",
174174 "success": true
175175 }
176176 ],
177177 [
178178 "Scoped Layers runs finalizers when the callback throws",
179179 {
180− "timestamp": "1771057450178.0",
180+ "timestamp": "1771057579324.0",
181181 "success": true
182182 }
183183 ],
184184 [
185185 "Scoped Layers threads finalizers through vertical composition (provide)",
186186 {
187− "timestamp": "1771057450178.0",
187+ "timestamp": "1771057579324.0",
188188 "success": true
189189 }
190190 ],
191191 [
192192 "Scoped Layers works with bracketLayer",
193193 {
194− "timestamp": "1771057450178.0",
194+ "timestamp": "1771057579324.0",
195195 "success": true
196196 }
197197 ],
198198 [
199199 "wireLayers memoizes shared dependencies within wireLayers",
200200 {
201− "timestamp": "1771057450178.0",
201+ "timestamp": "1771057579324.0",
202202 "success": true
203203 }
204204 ],
205205 [
206206 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
207207 {
208− "timestamp": "1771057450178.0",
208+ "timestamp": "1771057579324.0",
209209 "success": true
210210 }
211211 ],
212212 [
213213 "wireLayers wires a diamond dependency",
214214 {
215− "timestamp": "1771057450178.0",
215+ "timestamp": "1771057579324.0",
216216 "success": true
217217 }
218218 ],
219219 [
220220 "wireLayers wires a simple linear chain (A -> B -> C)",
221221 {
222− "timestamp": "1771057450178.0",
222+ "timestamp": "1771057579324.0",
223223 "success": true
224224 }
225225 ],
226226 [
227227 "wireLayers wires independent layers with no inter-dependencies",
228228 {
229− "timestamp": "1771057450178.0",
229+ "timestamp": "1771057579324.0",
230230 "success": true
231231 }
232232 ]
src/Yoga/Om/Layer.purs+98−23
@@ -27,11 +27,15 @@ module Yoga.Om.Layer
2727 , class FindProvider
2828 , class FindProviderMatch
2929 , class HasLabel
30− , class EmitEdges
30+ , class LayerDepth
31+ , class FindLayerReqs
32+ , class MaxProviderDepth
33+ , class MaxSymbol
3134 , class PrintLayersRL
3235 , class PrintEdgesRL
3336 , class IsNil
34− , class PrintLayer
37+ , class FormatNode
38+ , class ProviderNames
3539 , class CheckAllProvided
3640 , class CheckAllLabelsExist
3741 , class CheckLabelExists
@@ -610,40 +614,85 @@ instance HasLabel label Nil False
610614instance HasLabel label (Cons label ty tail) True
611615else instance HasLabel label tail result => HasLabel label (Cons sym ty tail) result
612616
613−-- | For a consumer layer, emit one edge per requirement: "provider ───▶ consumer"
614−class EmitEdges (consumer :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | consumer reqRL allLayers -> doc
617+-- | Compute depth of a layer: roots = "", each level adds " ".
618+class LayerDepth (sym :: Symbol) (allLayers :: RowList Type) (depth :: Symbol) | sym allLayers -> depth
615619
616−instance EmitEdges consumer Nil allLayers (Text "")
620+instance
621+ ( FindLayerReqs sym allLayers reqRL
622+ , FilterScope reqRL filteredReqRL
623+ , MaxProviderDepth filteredReqRL allLayers depth
624+ ) =>
625+ LayerDepth sym allLayers depth
626+
627+-- | Find the requirements RowList for a named layer.
628+class FindLayerReqs (sym :: Symbol) (rl :: RowList Type) (reqRL :: RowList Type) | sym rl -> reqRL
629+
630+instance
631+ ( RowToList req reqRL
632+ ) =>
633+ FindLayerReqs sym (Cons sym (OmLayer req err (Record prov)) tail) reqRL
617634
635+else instance
636+ FindLayerReqs sym tail reqRL =>
637+ FindLayerReqs sym (Cons other ty tail) reqRL
638+
639+-- | Compute max depth across all providers of a layer's requirements.
640+-- For each required label, find its provider, get provider's depth, add " ".
641+-- "Max" is approximated: we pick the longest depth symbol (deepest provider).
642+class MaxProviderDepth (reqRL :: RowList Type) (allLayers :: RowList Type) (depth :: Symbol) | reqRL allLayers -> depth
643+
644+-- No requirements: root layer, depth ""
645+instance MaxProviderDepth Nil allLayers ""
646+
647+-- One or more requirements: depth = provider's depth + " "
618648else instance
619649 ( FindProvider label allLayers provider
620− , Append " " provider s1
621− , Append s1 " ───▶ " s2
622− , Append s2 consumer line
623− , EmitEdges consumer tail allLayers restDoc
650+ , LayerDepth provider allLayers provDepth
651+ , Append provDepth " " nextDepth
652+ , MaxProviderDepth tail allLayers tailDepth
653+ , MaxSymbol nextDepth tailDepth depth
624654 ) =>
625− EmitEdges consumer (Cons label ty tail) allLayers (Above (Text line) restDoc)
655+ MaxProviderDepth (Cons label ty tail) allLayers depth
656+
657+-- | Pick the longer of two Symbols (approximating max depth).
658+class MaxSymbol (a :: Symbol) (b :: Symbol) (max :: Symbol) | a b -> max
659+
660+-- Both empty: result is empty
661+instance MaxSymbol "" "" ""
662+
663+-- a empty, b not: b wins
664+else instance MaxSymbol "" b b
665+
666+-- a not empty, b empty: a wins
667+else instance MaxSymbol a "" a
626668
627−-- | Walk the layers RowList, emitting edges for each layer with requirements.
669+-- Both non-empty: strip one char from each and recurse, then restore the winner
670+else instance
671+ ( Append " " restA a
672+ , Append " " restB b
673+ , MaxSymbol restA restB deeper
674+ , Append " " deeper max
675+ ) =>
676+ MaxSymbol a b max
677+
678+-- | Walk the layers RowList, printing each with indentation.
628679class PrintLayersRL (rl :: RowList Type) (doc :: Doc) | rl -> doc
629680class PrintEdgesRL (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
630681
631682instance PrintLayersRL Nil (Text "")
632−else instance
633− ( PrintEdgesRL rl rl doc
634− ) =>
635− PrintLayersRL rl doc
683+else instance PrintEdgesRL rl rl doc => PrintLayersRL rl doc
636684
637685instance PrintEdgesRL Nil allLayers (Text "")
638686
639687else instance
640688 ( RowToList req reqRL
641689 , FilterScope reqRL filteredReqRL
690+ , LayerDepth sym allLayers depth
642691 , IsNil filteredReqRL isRoot
643− , PrintLayer isRoot sym filteredReqRL allLayers edgeDoc
692+ , FormatNode isRoot depth sym filteredReqRL allLayers line
644693 , PrintEdgesRL tail allLayers restDoc
645694 ) =>
646− PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above edgeDoc restDoc)
695+ PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above (Text line) restDoc)
647696
648697-- | Check if a RowList is Nil.
649698class IsNil (rl :: RowList Type) (result :: Boolean) | rl -> result
@@ -651,18 +700,44 @@ class IsNil (rl :: RowList Type) (result :: Boolean) | rl -> result
651700instance IsNil Nil True
652701else instance IsNil (Cons sym ty tail) False
653702
654−-- | Print a single layer: root nodes as ○, others as edges.
655−class PrintLayer (isRoot :: Boolean) (sym :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | isRoot sym reqRL allLayers -> doc
703+-- | Format a node: root gets ○, others get ◀── providerList
704+class FormatNode (isRoot :: Boolean) (depth :: Symbol) (sym :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (line :: Symbol) | isRoot depth sym reqRL allLayers -> line
656705
706+-- Root node
657707instance
658− ( Append " ○ " sym line
708+ ( Append " " depth s1
709+ , Append s1 "○ " s2
710+ , Append s2 sym line
659711 ) =>
660− PrintLayer True sym Nil allLayers (Text line)
712+ FormatNode True depth sym Nil allLayers line
661713
714+-- Non-root: show providers ───▶ this
662715else instance
663− ( EmitEdges sym reqRL allLayers doc
716+ ( ProviderNames reqRL allLayers provSym
717+ , Append " " depth s1
718+ , Append s1 provSym s2
719+ , Append s2 " ───▶ " s3
720+ , Append s3 sym line
721+ ) =>
722+ FormatNode False depth sym reqRL allLayers line
723+
724+-- | Collect provider names for a set of requirements.
725+class ProviderNames (reqRL :: RowList Type) (allLayers :: RowList Type) (out :: Symbol) | reqRL allLayers -> out
726+
727+instance ProviderNames Nil allLayers ""
728+
729+else instance
730+ ( FindProvider label allLayers provider
731+ ) =>
732+ ProviderNames (Cons label ty Nil) allLayers provider
733+
734+else instance
735+ ( FindProvider label allLayers provider
736+ , ProviderNames tail allLayers rest
737+ , Append provider ", " s1
738+ , Append s1 rest out
664739 ) =>
665− PrintLayer False sym reqRL allLayers doc
740+ ProviderNames (Cons label ty tail) allLayers out
666741
667742wireLayersDebug
668743 :: forall layers rl req err prov doc