git.iko.soy/rowtype-yoga/purescript-yoga-om-strom
on main help sign in
491 lines 11 kB history raw
1# Strom Do-Notation Guide
3Yes! **Strom fully supports do-notation** because it implements the `Monad` type class.
5## Table of Contents
71. [Basic Do-Notation](#basic-do-notation)
82. [Comprehension-Style](#comprehension-style)
93. [Cartesian Products](#cartesian-products)
104. [Guards and Filtering](#guards-and-filtering)
115. [Applicative Do (ado)](#applicative-do-ado)
126. [Comparison with For-Comprehensions](#comparison-with-for-comprehensions)
14## Basic Do-Notation
16### Standard Monadic Bind
18```purescript
19import Yoga.Om.Strom as Strom
21-- FlatMap in do-notation
22stream :: Strom ctx err Int
23stream = do
24 x <- Strom.range 1 5
25 y <- Strom.range 1 3
26 pure (x * y)
28-- Equivalent to:
29stream' = Strom.range 1 5
30 >>= (\x ->
31 Strom.range 1 3
32 # Strom.map (\y -> x * y)
33 )
35-- Result: [1, 2, 3, 2, 4, 6, 3, 6, 9, 4, 8, 12]
36```
38### Sequential Processing
40```purescript
41processData :: Strom ctx err Result
42processData = do
43 userId <- userIds
44 user <- fetchUser userId -- Om effect
45 orders <- fetchOrders user.id -- Om effect
46 pure orders
48-- Each userId produces a stream of orders
49-- Result is flattened automatically
50```
52## Comprehension-Style
54### With Guards (Import from Strom.Do)
56```purescript
57import Yoga.Om.Strom.Do as Strom
59-- Pythagoras triples
60pythagoreanTriples :: Int -> Strom ctx err (Tuple3 Int Int Int)
61pythagoreanTriples n = do
62 a <- Strom.range 1 (n + 1)
63 b <- Strom.range a (n + 1)
64 c <- Strom.range b (n + 1)
65 Strom.guard (a * a + b * b == c * c)
66 pure (Tuple3 a b c)
68-- Result: [(3,4,5), (5,12,13), (6,8,10), (8,15,17), (9,12,15), ...]
69```
71### Even Numbers
73```purescript
74import Yoga.Om.Strom.Do (guard)
76evens :: Strom ctx err Int
77evens = do
78 x <- Strom.range 1 100
79 guard (x `mod` 2 == 0)
80 pure x
82-- Equivalent to:
83evens' = Strom.range 1 100
84 # Strom.filter (\x -> x `mod` 2 == 0)
85```
87### Prime Numbers
89```purescript
90import Yoga.Om.Strom.Do (guard)
92isPrime :: Int -> Boolean
93isPrime n = n > 1 && Array.all (\d -> n `mod` d /= 0) (Array.range 2 (n - 1))
95primes :: Int -> Strom ctx err Int
96primes upTo = do
97 n <- Strom.range 2 (upTo + 1)
98 guard (isPrime n)
99 pure n
101-- primes 20 -> [2, 3, 5, 7, 11, 13, 17, 19]
102```
104## Cartesian Products
106### Pairs (2D)
108```purescript
109import Data.Tuple (Tuple(..))
111allPairs :: Strom ctx err (Tuple Int Int)
112allPairs = do
113 x <- Strom.range 1 4
114 y <- Strom.range 1 3
115 pure (Tuple x y)
117-- Result: [(1,1), (1,2), (2,1), (2,2), (3,1), (3,2)]
119-- Or with helper:
120import Yoga.Om.Strom.Do (for2)
121allPairs' = for2 (Strom.range 1 4) (Strom.range 1 3) Tuple
122```
124### Triples (3D)
126```purescript
127allTriples :: Strom ctx err (Tuple3 Int Int Int)
128allTriples = do
129 x <- Strom.range 1 3
130 y <- Strom.range 1 3
131 z <- Strom.range 1 3
132 pure (Tuple3 x y z)
134-- Result: [(1,1,1), (1,1,2), (1,1,3), (1,2,1), ...]
136-- Or with helper:
137import Yoga.Om.Strom.Do (for3)
138allTriples' = for3
139 (Strom.range 1 3)
140 (Strom.range 1 3)
141 (Strom.range 1 3)
142 Tuple3
143```
145### Filtered Pairs
147```purescript
148import Yoga.Om.Strom.Do (guard)
150sumToN :: Int -> Strom ctx err (Tuple Int Int)
151sumToN n = do
152 x <- Strom.range 1 (n + 1)
153 y <- Strom.range 1 (n + 1)
154 guard (x + y == n)
155 pure (Tuple x y)
157-- sumToN 5 -> [(1,4), (2,3), (3,2), (4,1)]
158```
160## Guards and Filtering
162### Multiple Guards
164```purescript
165import Yoga.Om.Strom.Do (guard)
167complexFilter :: Strom ctx err Int
168complexFilter = do
169 x <- Strom.range 1 100
170 guard (x `mod` 2 == 0) -- Even
171 guard (x `mod` 3 == 0) -- Divisible by 3
172 guard (x < 50) -- Less than 50
173 pure x
175-- Result: [6, 12, 18, 24, 30, 36, 42, 48]
176```
178### When/Unless
180```purescript
181import Yoga.Om.Strom.Do (when, unless)
183conditionalStream :: Boolean -> Strom ctx err Int
184conditionalStream includeEvens = do
185 x <- Strom.range 1 10
186 when includeEvens do
187 guard (x `mod` 2 == 0)
188 pure x
189```
191## Applicative Do (ado)
193For **independent** parallel operations, use `ado` notation:
195```purescript
196-- Sequential (do) - second depends on first
197sequential :: Strom ctx err (Tuple User Orders)
198sequential = do
199 user <- fetchUser 123
200 orders <- fetchOrders user.id -- Depends on user!
201 pure (Tuple user orders)
203-- Parallel (ado) - independent
204parallel :: Strom ctx err (Tuple User Profile)
205parallel = ado
206 user <- fetchUser 123
207 profile <- fetchProfile 123 -- Independent!
208 in Tuple user profile
210-- With ado, both fetch concurrently
211```
213### Ado with Multiple Streams
215```purescript
216-- Zip-like behavior with ado
217combined :: Strom ctx err Result
218combined = ado
219 x <- stream1
220 y <- stream2
221 z <- stream3
222 in combine x y z
224-- This zips the three streams together
225```
227## Comparison with For-Comprehensions
229### Scala (ZIO ZStream)
231```scala
232// Scala
233val result = for {
234 x <- ZStream.range(1, 5)
235 y <- ZStream.range(1, 3)
236 if x % 2 == 0 // guard
237} yield x * y
238```
240### PureScript (Strom)
242```purescript
243-- PureScript
244import Yoga.Om.Strom.Do (guard)
246result = do
247 x <- Strom.range 1 5
248 y <- Strom.range 1 3
249 guard (x `mod` 2 == 0) -- guard
250 pure (x * y)
251```
253**Nearly identical!** ๐ŸŽ‰
255## Real-World Examples
257### API Pagination with Do-Notation
259```purescript
260import Yoga.Om.Strom.Do (guard)
262fetchAllUsers :: Strom Context () User
263fetchAllUsers = do
264 page <- Strom.iterate (_ + 1) 0
265 pageData <- Strom.fromOm (fetchPage page)
266 guard (not $ Array.null pageData.users)
267 user <- Strom.fromArray pageData.users
268 pure user
269```
271### Nested Data Processing
273```purescript
274processDepartments :: Strom Context () EmployeeReport
275processDepartments = do
276 dept <- departments
277 employee <- Strom.fromArray dept.employees
278 guard employee.active
279 projects <- Strom.fromArray employee.projects
280 guard (projects.status == "active")
281 pure { employee: employee.name, project: projects.name }
282```
284### Combinations with Constraints
286```purescript
287import Yoga.Om.Strom.Do (guard)
289-- All ways to make change for $1 using quarters, dimes, nickels
290makeChange :: Strom ctx err (Tuple3 Int Int Int)
291makeChange = do
292 quarters <- Strom.range 0 5 -- 0-4 quarters
293 dimes <- Strom.range 0 11 -- 0-10 dimes
294 nickels <- Strom.range 0 21 -- 0-20 nickels
295 let total = quarters * 25 + dimes * 10 + nickels * 5
296 guard (total == 100)
297 pure (Tuple3 quarters dimes nickels)
298```
300### Graph Traversal
302```purescript
303import Yoga.Om.Strom.Do (guard)
305traversePaths :: Graph -> Node -> Int -> Strom ctx err Path
306traversePaths graph start maxDepth = do
307 depth <- Strom.range 1 (maxDepth + 1)
308 guard (depth <= maxDepth)
309 node <- Strom.fromArray (graph.neighbors start)
310 path <- buildPath start node depth
311 pure path
312```
314## Advanced Patterns
316### Recursive Do-Notation
318```purescript
319fibonacci :: Strom ctx err Int
320fibonacci = do
321 pair <- Strom.iterate (\(Tuple a b) -> Tuple b (a + b)) (Tuple 0 1)
322 pure (fst pair)
324-- Take first 10: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
325```
327### Nested Comprehensions
329```purescript
330import Yoga.Om.Strom.Do (guard)
332nestedPatterns :: Strom ctx err (Tuple Int Int)
333nestedPatterns = do
334 x <- Strom.range 1 10
335 guard (x `mod` 2 == 0)
336 y <- do
337 n <- Strom.range 1 x
338 guard (n `mod` 2 /= 0)
339 pure n
340 pure (Tuple x y)
341```
343### Conditional Streaming
345```purescript
346import Yoga.Om.Strom.Do (when, unless)
348conditionalProcess :: Config -> Strom ctx err Result
349conditionalProcess config = do
350 item <- sourceStream
351 when config.enableValidation do
352 validated <- validateItem item
353 guard validated.isValid
354 when config.enableEnrichment do
355 enrichItem item
356 pure item
357```
359## Type Class Magic
361Because Strom implements these type classes:
363```purescript
364instance Functor (Strom ctx err) -- map, <$>
365instance Apply (Strom ctx err) -- <*>, apply
366instance Applicative (Strom ctx err) -- pure
367instance Bind (Strom ctx err) -- >>=, bind
368instance Monad (Strom ctx err) -- do-notation
369```
371You get all these for free:
373- โœ… `do` notation
374- โœ… `ado` notation
375- โœ… `<$>` (map)
376- โœ… `<*>` (apply)
377- โœ… `>>=` (bind)
378- โœ… `>>` (discard)
380## Performance Considerations
382### Do-Notation = FlatMap
384```purescript
385-- This:
386do
387 x <- stream1
388 y <- stream2
389 pure (x + y)
391-- Is equivalent to:
392stream1 >>= (\x ->
393 stream2 # Strom.map (\y -> x + y)
395```
397For large Cartesian products, this can be expensive!
399### When to Use Do-Notation
401โœ… **Good Use Cases:**
402- Dependent operations (next depends on previous)
403- Comprehension-style filtering
404- Graph traversal
405- Tree structures
406- Small Cartesian products
408โŒ **Avoid For:**
409- Large Cartesian products (use `zip` instead)
410- Independent operations (use `ado` or `mapParallel`)
411- Simple transformations (use `map`)
413### Optimization Example
415```purescript
416-- โŒ Slow for large streams
417slowPairs = do
418 x <- Strom.range 1 10000
419 y <- Strom.range 1 10000
420 pure (Tuple x y)
422-- โœ… Much faster
423fastPairs = Strom.zip
424 (Strom.range 1 10000)
425 (Strom.range 1 10000)
426```
428## Helper Module: Yoga.Om.Strom.Do
430Import the helper module for comprehension-style programming:
432```purescript
433import Yoga.Om.Strom.Do as Strom
435myStream = do
436 x <- Strom.range 1 10
437 Strom.guard (x `mod` 2 == 0)
438 y <- Strom.range 1 x
439 pure (Tuple x y)
440```
442### Available Helpers
444- `guard :: Boolean -> Strom ctx err Unit` - Filter in do-notation
445- `when :: Boolean -> Strom ctx err a -> Strom ctx err a` - Conditional
446- `unless :: Boolean -> Strom ctx err a -> Strom ctx err a` - Negated conditional
447- `for2 :: Strom a -> Strom b -> (a -> b -> c) -> Strom c` - 2D comprehension
448- `for3 :: Strom a -> Strom b -> Strom c -> (a -> b -> c -> d) -> Strom d` - 3D comprehension
449- `replicateEach :: Int -> a -> Strom a` - Replicate element
450- `comprehension :: Strom a -> Strom a` - Identity (for clarity)
451- `generate :: Strom a -> Strom a` - Identity (for clarity)
453## Summary
455### โœ… Yes, Strom Has Full Do-Notation Support!
457Because Strom implements `Monad`, you get:
4591. **Regular do-notation** - For dependent operations
4602. **Applicative do (ado)** - For independent parallel operations
4613. **Comprehension-style** - With guards and filtering
4624. **Cartesian products** - Natural in do-notation
4635. **All monadic combinators** - `>>=`, `>>`, `<$>`, `<*>`, etc.
465### Compare: Scala vs PureScript
467```scala
468// Scala ZIO ZStream
469for {
470 x <- ZStream.range(1, 10)
471 y <- ZStream.range(1, x)
472 if x % 2 == 0
473} yield (x, y)
474```
476```purescript
477-- PureScript Strom
478import Yoga.Om.Strom.Do (guard)
480do
481 x <- Strom.range 1 10
482 y <- Strom.range 1 x
483 guard (x `mod` 2 == 0)
484 pure (Tuple x y)
485```
487**Nearly identical!** ๐ŸŽ‰
489---
491**Strom + Do-Notation = Powerful Stream Comprehensions!** ๐ŸŒŠ