git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
commit00a73db1208d6c3202bc389a4f6a4239f841caeb authorMark <mark.eibes@gmail.com> dateSat 14 Feb 2026, 09:24 (8 months ago) parentd5bf665
2 files changed+101−87
.spec-results+33−33 src/Yoga/Om/Layer.purs+68−54
.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": "1771057250077.0",
5+ "timestamp": "1771057450178.0",
66 "success": true
77 }
88 ],
99 [
1010 "Automatic Memoization memoized bracketLayer acquires and releases only once",
1111 {
12− "timestamp": "1771057250077.0",
12+ "timestamp": "1771057450178.0",
1313 "success": true
1414 }
1515 ],
1616 [
1717 "Automatic Memoization memoized scoped layer runs finalizer only once",
1818 {
19− "timestamp": "1771057250077.0",
19+ "timestamp": "1771057450178.0",
2020 "success": true
2121 }
2222 ],
2323 [
2424 "Composition proves Row.Nub deduplicates shared requirements at type level",
2525 {
26− "timestamp": "1771057250077.0",
26+ "timestamp": "1771057450178.0",
2727 "success": true
2828 }
2929 ],
3030 [
3131 "Composition proves deduplication at runtime via shared Ref",
3232 {
33− "timestamp": "1771057250077.0",
33+ "timestamp": "1771057450178.0",
3434 "success": true
3535 }
3636 ],
3737 [
3838 "Error Channel composes layers with different error types",
3939 {
40− "timestamp": "1771057250077.0",
40+ "timestamp": "1771057450178.0",
4141 "success": true
4242 }
4343 ],
4444 [
4545 "Error Channel propagates errors from the first layer",
4646 {
47− "timestamp": "1771057250077.0",
47+ "timestamp": "1771057450178.0",
4848 "success": true
4949 }
5050 ],
5151 [
5252 "Error Channel propagates errors from the second layer",
5353 {
54− "timestamp": "1771057250077.0",
54+ "timestamp": "1771057450178.0",
5555 "success": true
5656 }
5757 ],
5858 [
5959 "Error Channel propagates errors through vertical composition",
6060 {
61− "timestamp": "1771057250077.0",
61+ "timestamp": "1771057450178.0",
6262 "success": true
6363 }
6464 ],
6565 [
6666 "Error Channel scoped layer error triggers finalizers",
6767 {
68− "timestamp": "1771057250077.0",
68+ "timestamp": "1771057450178.0",
6969 "success": true
7070 }
7171 ],
7272 [
7373 "Error Channel scoped layers with errors run via runScopedWith",
7474 {
75− "timestamp": "1771057250077.0",
75+ "timestamp": "1771057450178.0",
7676 "success": true
7777 }
7878 ],
7979 [
8080 "Functor / Apply / Bind applies two layers",
8181 {
82− "timestamp": "1771057250077.0",
82+ "timestamp": "1771057450178.0",
8383 "success": true
8484 }
8585 ],
8686 [
8787 "Functor / Apply / Bind binds layers sequentially",
8888 {
89− "timestamp": "1771057250077.0",
89+ "timestamp": "1771057450178.0",
9090 "success": true
9191 }
9292 ],
9393 [
9494 "Functor / Apply / Bind maps over layer output",
9595 {
96− "timestamp": "1771057250077.0",
96+ "timestamp": "1771057450178.0",
9797 "success": true
9898 }
9999 ],
100100 [
101101 "Layer recovering does not retry on non-matching errors",
102102 {
103− "timestamp": "1771057250077.0",
103+ "timestamp": "1771057450178.0",
104104 "success": true
105105 }
106106 ],
107107 [
108108 "Layer recovering only retries the specified error in a multi-error layer",
109109 {
110− "timestamp": "1771057250077.0",
110+ "timestamp": "1771057450178.0",
111111 "success": true
112112 }
113113 ],
114114 [
115115 "Layer recovering preserves the error when retries are exhausted",
116116 {
117− "timestamp": "1771057250077.0",
117+ "timestamp": "1771057450178.0",
118118 "success": true
119119 }
120120 ],
121121 [
122122 "Layer recovering retries layer construction on matching errors",
123123 {
124− "timestamp": "1771057250077.0",
124+ "timestamp": "1771057450178.0",
125125 "success": true
126126 }
127127 ],
128128 [
129129 "Layer recovering uses RetryStatus in the check",
130130 {
131− "timestamp": "1771057250077.0",
131+ "timestamp": "1771057450178.0",
132132 "success": true
133133 }
134134 ],
135135 [
136136 "Layer repeating repeats layer construction while condition holds",
137137 {
138− "timestamp": "1771057250077.0",
138+ "timestamp": "1771057450178.0",
139139 "success": true
140140 }
141141 ],
142142 [
143143 "Layer repeating stops when policy exhausted",
144144 {
145− "timestamp": "1771057250077.0",
145+ "timestamp": "1771057450178.0",
146146 "success": true
147147 }
148148 ],
149149 [
150150 "Parallel runs layers in parallel via parApply",
151151 {
152− "timestamp": "1771057250077.0",
152+ "timestamp": "1771057450178.0",
153153 "success": true
154154 }
155155 ],
156156 [
157157 "Scoped Layers combines scoped and non-scoped layers",
158158 {
159− "timestamp": "1771057250077.0",
159+ "timestamp": "1771057450178.0",
160160 "success": true
161161 }
162162 ],
163163 [
164164 "Scoped Layers fresh layer builds twice even when same base layer",
165165 {
166− "timestamp": "1771057250077.0",
166+ "timestamp": "1771057450178.0",
167167 "success": true
168168 }
169169 ],
170170 [
171171 "Scoped Layers runs finalizers in reverse order on success",
172172 {
173− "timestamp": "1771057250077.0",
173+ "timestamp": "1771057450178.0",
174174 "success": true
175175 }
176176 ],
177177 [
178178 "Scoped Layers runs finalizers when the callback throws",
179179 {
180− "timestamp": "1771057250077.0",
180+ "timestamp": "1771057450178.0",
181181 "success": true
182182 }
183183 ],
184184 [
185185 "Scoped Layers threads finalizers through vertical composition (provide)",
186186 {
187− "timestamp": "1771057250077.0",
187+ "timestamp": "1771057450178.0",
188188 "success": true
189189 }
190190 ],
191191 [
192192 "Scoped Layers works with bracketLayer",
193193 {
194− "timestamp": "1771057250077.0",
194+ "timestamp": "1771057450178.0",
195195 "success": true
196196 }
197197 ],
198198 [
199199 "wireLayers memoizes shared dependencies within wireLayers",
200200 {
201− "timestamp": "1771057250077.0",
201+ "timestamp": "1771057450178.0",
202202 "success": true
203203 }
204204 ],
205205 [
206206 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
207207 {
208− "timestamp": "1771057250077.0",
208+ "timestamp": "1771057450178.0",
209209 "success": true
210210 }
211211 ],
212212 [
213213 "wireLayers wires a diamond dependency",
214214 {
215− "timestamp": "1771057250077.0",
215+ "timestamp": "1771057450178.0",
216216 "success": true
217217 }
218218 ],
219219 [
220220 "wireLayers wires a simple linear chain (A -> B -> C)",
221221 {
222− "timestamp": "1771057250077.0",
222+ "timestamp": "1771057450178.0",
223223 "success": true
224224 }
225225 ],
226226 [
227227 "wireLayers wires independent layers with no inter-dependencies",
228228 {
229− "timestamp": "1771057250077.0",
229+ "timestamp": "1771057450178.0",
230230 "success": true
231231 }
232232 ]
src/Yoga/Om/Layer.purs+68−54
@@ -24,9 +24,14 @@ module Yoga.Om.Layer
2424 , class WireLayersRL
2525 , class RowLabels
2626 , class FilterScope
27− , class FormatLayer
27+ , class FindProvider
28+ , class FindProviderMatch
29+ , class HasLabel
30+ , class EmitEdges
2831 , class PrintLayersRL
29− , class PrintLayersInner
32+ , class PrintEdgesRL
33+ , class IsNil
34+ , class PrintLayer
3035 , class CheckAllProvided
3136 , class CheckAllLabelsExist
3237 , class CheckLabelExists
@@ -576,79 +581,88 @@ instance FilterScope Nil Nil
576581instance FilterScope (Cons "scope" Scope tail) tail
577582else instance FilterScope tail out => FilterScope (Cons sym ty tail) (Cons sym ty out)
578583
579−-- | Format a single layer line.
580−class FormatLayer (prefix :: Symbol) (sym :: Symbol) (reqRL :: RowList Type) (provRL :: RowList Type) (line :: Symbol) | prefix sym reqRL provRL -> line
584+-- | Given a label, find which layer in the RowList provides it.
585+class FindProvider (label :: Symbol) (rl :: RowList Type) (provider :: Symbol) | label rl -> provider
581586
582−-- No deps (after filtering scope)
587+-- This layer provides the label
583588instance
584− ( RowLabels provRL provSym
585− , Append prefix sym s1
586− , Append s1 " ─▶ " s2
587− , Append s2 provSym line
589+ ( RowToList prov provRL
590+ , HasLabel label provRL hasIt
591+ , FindProviderMatch hasIt label sym (Cons sym (OmLayer req err (Record prov)) tail) provider
588592 ) =>
589− FormatLayer prefix sym Nil provRL line
593+ FindProvider label (Cons sym (OmLayer req err (Record prov)) tail) provider
590594
591−-- Has deps
595+-- | Dispatch based on whether the current layer has the label.
596+class FindProviderMatch (hasIt :: Boolean) (label :: Symbol) (sym :: Symbol) (rl :: RowList Type) (provider :: Symbol) | hasIt label sym rl -> provider
597+
598+-- Yes: this layer provides it
599+instance FindProviderMatch True label sym rl sym
600+
601+-- No: keep looking in the tail
592602else instance
593− ( RowLabels reqRL reqSym
594− , RowLabels provRL provSym
595− , Append prefix sym s1
596− , Append s1 " ─▶ " s2
597− , Append s2 provSym s3
598− , Append s3 " ◂── " s4
599− , Append s4 reqSym line
603+ FindProvider label tail provider =>
604+ FindProviderMatch False label sym (Cons sym ty tail) provider
605+
606+-- | Check if a RowList contains a given label.
607+class HasLabel (label :: Symbol) (rl :: RowList Type) (result :: Boolean) | label rl -> result
608+
609+instance HasLabel label Nil False
610+instance HasLabel label (Cons label ty tail) True
611+else instance HasLabel label tail result => HasLabel label (Cons sym ty tail) result
612+
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
615+
616+instance EmitEdges consumer Nil allLayers (Text "")
617+
618+else instance
619+ ( FindProvider label allLayers provider
620+ , Append " " provider s1
621+ , Append s1 " ───▶ " s2
622+ , Append s2 consumer line
623+ , EmitEdges consumer tail allLayers restDoc
600624 ) =>
601− FormatLayer prefix sym reqRL provRL line
625+ EmitEdges consumer (Cons label ty tail) allLayers (Above (Text line) restDoc)
602626
603−-- | Walk the layers RowList and build a Doc with tree connectors.
604−-- Uses a helper with a Boolean flag for "is first element".
627+-- | Walk the layers RowList, emitting edges for each layer with requirements.
605628class PrintLayersRL (rl :: RowList Type) (doc :: Doc) | rl -> doc
606−class PrintLayersInner (isFirst :: Boolean) (rl :: RowList Type) (doc :: Doc) | isFirst rl -> doc
629+class PrintEdgesRL (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
607630
608631instance PrintLayersRL Nil (Text "")
609−else instance PrintLayersInner True rl doc => PrintLayersRL rl doc
610−
611−-- Single remaining item when first: ─── (only layer)
612−instance
613− ( IsSymbol sym
614− , RowToList req reqRL
615− , FilterScope reqRL filteredReqRL
616− , RowToList prov provRL
617− , FormatLayer " ─── " sym filteredReqRL provRL line
632+else instance
633+ ( PrintEdgesRL rl rl doc
618634 ) =>
619− PrintLayersInner True (Cons sym (OmLayer req err (Record prov)) Nil) (Text line)
635+ PrintLayersRL rl doc
636+
637+instance PrintEdgesRL Nil allLayers (Text "")
620638
621−-- First item with more to come: ┌──
622639else instance
623− ( IsSymbol sym
624− , RowToList req reqRL
640+ ( RowToList req reqRL
625641 , FilterScope reqRL filteredReqRL
626− , RowToList prov provRL
627− , FormatLayer " ┌── " sym filteredReqRL provRL line
628− , PrintLayersInner False tail restDoc
642+ , IsNil filteredReqRL isRoot
643+ , PrintLayer isRoot sym filteredReqRL allLayers edgeDoc
644+ , PrintEdgesRL tail allLayers restDoc
629645 ) =>
630− PrintLayersInner True (Cons sym (OmLayer req err (Record prov)) tail) (Above (Text line) restDoc)
646+ PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above edgeDoc restDoc)
647+
648+-- | Check if a RowList is Nil.
649+class IsNil (rl :: RowList Type) (result :: Boolean) | rl -> result
650+
651+instance IsNil Nil True
652+else instance IsNil (Cons sym ty tail) False
653+
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
631656
632−-- Last item: └──
633657instance
634− ( IsSymbol sym
635− , RowToList req reqRL
636− , FilterScope reqRL filteredReqRL
637− , RowToList prov provRL
638− , FormatLayer " └── " sym filteredReqRL provRL line
658+ ( Append " ○ " sym line
639659 ) =>
640− PrintLayersInner False (Cons sym (OmLayer req err (Record prov)) Nil) (Text line)
660+ PrintLayer True sym Nil allLayers (Text line)
641661
642−-- Middle item: ├──
643662else instance
644− ( IsSymbol sym
645− , RowToList req reqRL
646− , FilterScope reqRL filteredReqRL
647− , RowToList prov provRL
648− , FormatLayer " ├── " sym filteredReqRL provRL line
649− , PrintLayersInner False tail restDoc
663+ ( EmitEdges sym reqRL allLayers doc
650664 ) =>
651− PrintLayersInner False (Cons sym (OmLayer req err (Record prov)) tail) (Above (Text line) restDoc)
665+ PrintLayer False sym reqRL allLayers doc
652666
653667wireLayersDebug
654668 :: forall layers rl req err prov doc