git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
commit716c69b8f39a85a7c07fe11b4530778f96f3809e authorMark <mark.eibes@gmail.com> dateSat 14 Feb 2026, 18:45 (8 months ago) parent1be7a11
2 files changed+46−167
.spec-results+33−33 src/Yoga/Om/Layer.purs+13−134
.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": "1771090955841.0",
5+ "timestamp": "1771091090120.0",
66 "success": true
77 }
88 ],
99 [
1010 "Automatic Memoization memoized bracketLayer acquires and releases only once",
1111 {
12− "timestamp": "1771090955841.0",
12+ "timestamp": "1771091090120.0",
1313 "success": true
1414 }
1515 ],
1616 [
1717 "Automatic Memoization memoized scoped layer runs finalizer only once",
1818 {
19− "timestamp": "1771090955841.0",
19+ "timestamp": "1771091090120.0",
2020 "success": true
2121 }
2222 ],
2323 [
2424 "Composition proves Row.Nub deduplicates shared requirements at type level",
2525 {
26− "timestamp": "1771090955841.0",
26+ "timestamp": "1771091090120.0",
2727 "success": true
2828 }
2929 ],
3030 [
3131 "Composition proves deduplication at runtime via shared Ref",
3232 {
33− "timestamp": "1771090955841.0",
33+ "timestamp": "1771091090120.0",
3434 "success": true
3535 }
3636 ],
3737 [
3838 "Error Channel composes layers with different error types",
3939 {
40− "timestamp": "1771090955841.0",
40+ "timestamp": "1771091090120.0",
4141 "success": true
4242 }
4343 ],
4444 [
4545 "Error Channel propagates errors from the first layer",
4646 {
47− "timestamp": "1771090955841.0",
47+ "timestamp": "1771091090120.0",
4848 "success": true
4949 }
5050 ],
5151 [
5252 "Error Channel propagates errors from the second layer",
5353 {
54− "timestamp": "1771090955841.0",
54+ "timestamp": "1771091090120.0",
5555 "success": true
5656 }
5757 ],
5858 [
5959 "Error Channel propagates errors through vertical composition",
6060 {
61− "timestamp": "1771090955841.0",
61+ "timestamp": "1771091090120.0",
6262 "success": true
6363 }
6464 ],
6565 [
6666 "Error Channel scoped layer error triggers finalizers",
6767 {
68− "timestamp": "1771090955841.0",
68+ "timestamp": "1771091090120.0",
6969 "success": true
7070 }
7171 ],
7272 [
7373 "Error Channel scoped layers with errors run via runScopedWith",
7474 {
75− "timestamp": "1771090955841.0",
75+ "timestamp": "1771091090120.0",
7676 "success": true
7777 }
7878 ],
7979 [
8080 "Functor / Apply / Bind applies two layers",
8181 {
82− "timestamp": "1771090955841.0",
82+ "timestamp": "1771091090120.0",
8383 "success": true
8484 }
8585 ],
8686 [
8787 "Functor / Apply / Bind binds layers sequentially",
8888 {
89− "timestamp": "1771090955841.0",
89+ "timestamp": "1771091090120.0",
9090 "success": true
9191 }
9292 ],
9393 [
9494 "Functor / Apply / Bind maps over layer output",
9595 {
96− "timestamp": "1771090955841.0",
96+ "timestamp": "1771091090120.0",
9797 "success": true
9898 }
9999 ],
100100 [
101101 "Layer recovering does not retry on non-matching errors",
102102 {
103− "timestamp": "1771090955841.0",
103+ "timestamp": "1771091090120.0",
104104 "success": true
105105 }
106106 ],
107107 [
108108 "Layer recovering only retries the specified error in a multi-error layer",
109109 {
110− "timestamp": "1771090955841.0",
110+ "timestamp": "1771091090120.0",
111111 "success": true
112112 }
113113 ],
114114 [
115115 "Layer recovering preserves the error when retries are exhausted",
116116 {
117− "timestamp": "1771090955841.0",
117+ "timestamp": "1771091090120.0",
118118 "success": true
119119 }
120120 ],
121121 [
122122 "Layer recovering retries layer construction on matching errors",
123123 {
124− "timestamp": "1771090955841.0",
124+ "timestamp": "1771091090120.0",
125125 "success": true
126126 }
127127 ],
128128 [
129129 "Layer recovering uses RetryStatus in the check",
130130 {
131− "timestamp": "1771090955841.0",
131+ "timestamp": "1771091090120.0",
132132 "success": true
133133 }
134134 ],
135135 [
136136 "Layer repeating repeats layer construction while condition holds",
137137 {
138− "timestamp": "1771090955841.0",
138+ "timestamp": "1771091090120.0",
139139 "success": true
140140 }
141141 ],
142142 [
143143 "Layer repeating stops when policy exhausted",
144144 {
145− "timestamp": "1771090955841.0",
145+ "timestamp": "1771091090120.0",
146146 "success": true
147147 }
148148 ],
149149 [
150150 "Parallel runs layers in parallel via parApply",
151151 {
152− "timestamp": "1771090955841.0",
152+ "timestamp": "1771091090120.0",
153153 "success": true
154154 }
155155 ],
156156 [
157157 "Scoped Layers combines scoped and non-scoped layers",
158158 {
159− "timestamp": "1771090955841.0",
159+ "timestamp": "1771091090120.0",
160160 "success": true
161161 }
162162 ],
163163 [
164164 "Scoped Layers fresh layer builds twice even when same base layer",
165165 {
166− "timestamp": "1771090955841.0",
166+ "timestamp": "1771091090120.0",
167167 "success": true
168168 }
169169 ],
170170 [
171171 "Scoped Layers runs finalizers in reverse order on success",
172172 {
173− "timestamp": "1771090955841.0",
173+ "timestamp": "1771091090120.0",
174174 "success": true
175175 }
176176 ],
177177 [
178178 "Scoped Layers runs finalizers when the callback throws",
179179 {
180− "timestamp": "1771090955841.0",
180+ "timestamp": "1771091090120.0",
181181 "success": true
182182 }
183183 ],
184184 [
185185 "Scoped Layers threads finalizers through vertical composition (provide)",
186186 {
187− "timestamp": "1771090955841.0",
187+ "timestamp": "1771091090120.0",
188188 "success": true
189189 }
190190 ],
191191 [
192192 "Scoped Layers works with bracketLayer",
193193 {
194− "timestamp": "1771090955841.0",
194+ "timestamp": "1771091090120.0",
195195 "success": true
196196 }
197197 ],
198198 [
199199 "wireLayers memoizes shared dependencies within wireLayers",
200200 {
201− "timestamp": "1771090955841.0",
201+ "timestamp": "1771091090120.0",
202202 "success": true
203203 }
204204 ],
205205 [
206206 "wireLayers wireLayersDebug emits dependency graph as compiler warning",
207207 {
208− "timestamp": "1771090955841.0",
208+ "timestamp": "1771091090120.0",
209209 "success": true
210210 }
211211 ],
212212 [
213213 "wireLayers wires a diamond dependency",
214214 {
215− "timestamp": "1771090955841.0",
215+ "timestamp": "1771091090120.0",
216216 "success": true
217217 }
218218 ],
219219 [
220220 "wireLayers wires a simple linear chain (A -> B -> C)",
221221 {
222− "timestamp": "1771090955841.0",
222+ "timestamp": "1771091090120.0",
223223 "success": true
224224 }
225225 ],
226226 [
227227 "wireLayers wires independent layers with no inter-dependencies",
228228 {
229− "timestamp": "1771090955841.0",
229+ "timestamp": "1771091090120.0",
230230 "success": true
231231 }
232232 ]
src/Yoga/Om/Layer.purs+13−134
@@ -22,20 +22,13 @@ module Yoga.Om.Layer
2222 , wireLayersDebug
2323 , wireLayersRL
2424 , class WireLayersRL
25− , class RowLabels
2625 , class FilterScope
2726 , class FindProvider
2827 , class FindProviderMatch
2928 , class HasLabel
30− , class LayerDepth
31− , class FindLayerReqs
32− , class MaxProviderDepth
33− , class MaxSymbol
29+ , class EmitEdges
3430 , class PrintLayersRL
3531 , class PrintEdgesRL
36− , class IsNil
37− , class FormatNode
38− , class ProviderNames
3932 , class CheckAllProvided
4033 , class CheckAllLabelsExist
4134 , class CheckLabelExists
@@ -560,24 +553,9 @@ wireLayers layers =
560553 seed = pure {} :: OmLayer (scope :: Scope) () (Record ())
561554
562555-- =============================================================================
563−-- Debug graph printing
556+-- Debug graph printing (D2 diagram)
564557-- =============================================================================
565558
566−-- | Collect labels from a RowList into a comma-separated Symbol.
567−class RowLabels (rl :: RowList Type) (out :: Symbol) | rl -> out
568−
569−instance RowLabels Nil ""
570−
571−else instance IsSymbol sym => RowLabels (Cons sym ty Nil) sym
572−
573−else instance
574− ( IsSymbol sym
575− , RowLabels tail rest
576− , Append sym ", " symComma
577− , Append symComma rest out
578− ) =>
579− RowLabels (Cons sym ty tail) out
580−
581559-- | Remove `scope :: Scope` from a RowList (it's always present, just noise).
582560class FilterScope (rl :: RowList Type) (out :: RowList Type) | rl -> out
583561
@@ -588,7 +566,6 @@ else instance FilterScope tail out => FilterScope (Cons sym ty tail) (Cons sym t
588566-- | Given a label, find which layer in the RowList provides it.
589567class FindProvider (label :: Symbol) (rl :: RowList Type) (provider :: Symbol) | label rl -> provider
590568
591−-- This layer provides the label
592569instance
593570 ( RowToList prov provRL
594571 , HasLabel label provRL hasIt
@@ -599,10 +576,8 @@ instance
599576-- | Dispatch based on whether the current layer has the label.
600577class FindProviderMatch (hasIt :: Boolean) (label :: Symbol) (sym :: Symbol) (rl :: RowList Type) (provider :: Symbol) | hasIt label sym rl -> provider
601578
602−-- Yes: this layer provides it
603579instance FindProviderMatch True label sym rl sym
604580
605−-- No: keep looking in the tail
606581else instance
607582 FindProvider label tail provider =>
608583 FindProviderMatch False label sym (Cons sym ty tail) provider
@@ -614,68 +589,20 @@ instance HasLabel label Nil False
614589instance HasLabel label (Cons label ty tail) True
615590else instance HasLabel label tail result => HasLabel label (Cons sym ty tail) result
616591
617−-- | Compute depth of a layer: roots = "", each level adds " ".
618−class LayerDepth (sym :: Symbol) (allLayers :: RowList Type) (depth :: Symbol) | sym allLayers -> depth
619−
620−instance
621− ( FindLayerReqs sym allLayers reqRL
622− , FilterScope reqRL filteredReqRL
623− , MaxProviderDepth filteredReqRL allLayers depth
624− ) =>
625− LayerDepth sym allLayers depth
592+-- | Emit D2 edges for one consumer: "provider -> consumer" per requirement.
593+class EmitEdges (consumer :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | consumer reqRL allLayers -> doc
626594
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
595+instance EmitEdges consumer Nil allLayers (Text "")
634596
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 + " "
648597else instance
649598 ( FindProvider label allLayers provider
650− , LayerDepth provider allLayers provDepth
651− , Append provDepth " " nextDepth
652− , MaxProviderDepth tail allLayers tailDepth
653− , MaxSymbol nextDepth tailDepth depth
599+ , Append provider " -> " s1
600+ , Append s1 consumer line
601+ , EmitEdges consumer tail allLayers restDoc
654602 ) =>
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
603+ EmitEdges consumer (Cons label ty tail) allLayers (Above (Text line) restDoc)
668604
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.
605+-- | Walk all layers emitting D2 edges.
679606class PrintLayersRL (rl :: RowList Type) (doc :: Doc) | rl -> doc
680607class PrintEdgesRL (rl :: RowList Type) (allLayers :: RowList Type) (doc :: Doc) | rl allLayers -> doc
681608
@@ -687,64 +614,16 @@ instance PrintEdgesRL Nil allLayers (Text "")
687614else instance
688615 ( RowToList req reqRL
689616 , FilterScope reqRL filteredReqRL
690− , LayerDepth sym allLayers depth
691− , IsNil filteredReqRL isRoot
692− , FormatNode isRoot depth sym filteredReqRL allLayers line
617+ , EmitEdges sym filteredReqRL allLayers edgeDoc
693618 , PrintEdgesRL tail allLayers restDoc
694619 ) =>
695− PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above (Text line) restDoc)
696−
697−-- | Check if a RowList is Nil.
698−class IsNil (rl :: RowList Type) (result :: Boolean) | rl -> result
699−
700−instance IsNil Nil True
701−else instance IsNil (Cons sym ty tail) False
702−
703−-- | Format a node: each layer appears once, indented by depth, with providers listed.
704−class FormatNode (isRoot :: Boolean) (depth :: Symbol) (sym :: Symbol) (reqRL :: RowList Type) (allLayers :: RowList Type) (line :: Symbol) | isRoot depth sym reqRL allLayers -> line
705−
706−-- Root node
707−instance
708− ( Append " " depth s1
709− , Append s1 "○ " s2
710− , Append s2 sym line
711− ) =>
712− FormatNode True depth sym Nil allLayers line
713−
714−-- Non-root: show this ◂── providerList
715−else instance
716− ( ProviderNames reqRL allLayers provSym
717− , Append " " depth s1
718− , Append s1 "○ " s2
719− , Append s2 sym s3
720− , Append s3 " ◂── " s4
721− , Append s4 provSym line
722− ) =>
723− FormatNode False depth sym reqRL allLayers line
724−
725−-- | Collect provider names for a set of requirements.
726−class ProviderNames (reqRL :: RowList Type) (allLayers :: RowList Type) (out :: Symbol) | reqRL allLayers -> out
727−
728−instance ProviderNames Nil allLayers ""
729−
730−else instance
731− ( FindProvider label allLayers provider
732− ) =>
733− ProviderNames (Cons label ty Nil) allLayers provider
734−
735−else instance
736− ( FindProvider label allLayers provider
737− , ProviderNames tail allLayers rest
738− , Append provider ", " s1
739− , Append s1 rest out
740− ) =>
741− ProviderNames (Cons label ty tail) allLayers out
620+ PrintEdgesRL (Cons sym (OmLayer req err (Record prov)) tail) allLayers (Above edgeDoc restDoc)
742621
743622wireLayersDebug
744623 :: forall layers rl req err prov doc
745624 . RowToList layers rl
746625 => PrintLayersRL rl doc
747− => Warn (Above (Text "") (Above (Text "Layer wiring:") doc))
626+ => Warn (Above (Text "") (Above (Text "Copy into https://play.d2lang.com") doc))
748627 => WireLayersRL rl layers (scope :: Scope) () () req err prov
749628 => Record layers
750629 -> OmLayer req err (Record prov)