Skip to content

Commit

Permalink
Merge pull request #412 from andrewMacmurray/stack-safe-helpers
Browse files Browse the repository at this point in the history
Makes BackendTask.combine stack safe
  • Loading branch information
dillonkearns authored Sep 24, 2023
2 parents 86fe340 + 9b40c77 commit daeb4f4
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/BackendTask.elm
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Any place in your `elm-pages` app where the framework lets you pass in a value o

import FatalError exposing (FatalError)
import Json.Encode
import List.Chunks
import Pages.StaticHttpRequest exposing (RawRequest(..))


Expand Down Expand Up @@ -175,6 +176,26 @@ resolve =
-}
combine : List (BackendTask error value) -> BackendTask error (List value)
combine items =
items
|> List.Chunks.chunk 100
|> List.map combineHelp
|> List.Chunks.chunk 100
|> List.map combineHelp
|> List.Chunks.chunk 100
|> List.map combineHelp
|> combineHelp
|> map (List.concat >> List.concat >> List.concat)


{-| `combineHelp` on its own will overflow the stack with larger lists of tasks
dividing the tasks into smaller nested chunks and recombining makes `combine` stack safe
There's probably a way of doing this without the Lists but it's a neat trick to safely combine lots of tasks!
-}
combineHelp : List (BackendTask error value) -> BackendTask error (List value)
combineHelp items =
List.foldl (map2 (::)) (succeed []) items |> map List.reverse


Expand Down
51 changes: 51 additions & 0 deletions src/List/Chunks.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module List.Chunks exposing (chunk)

import Array exposing (Array)


{-| Adapted from <https://package.elm-lang.org/packages/krisajenkins/elm-exts/latest/Exts-List>
Split a list into chunks of length `n`.
Be aware that the last sub-list may be smaller than `n`-items long.
For example `chunk 3 [1..10] => [[1,2,3], [4,5,6], [7,8,9], [10]]`
-}
chunk : Int -> List a -> List (List a)
chunk n xs =
if n < 1 then
List.singleton xs

else
evaluate (chunkInternal n xs Array.empty)


chunkInternal : Int -> List a -> Array (List a) -> Trampoline (List (List a))
chunkInternal n xs acc =
-- elm-review: known-unoptimized-recursion
if List.isEmpty xs then
Done (Array.toList acc)

else
Jump
(\_ ->
chunkInternal n
(List.drop n xs)
(Array.push (List.take n xs) acc)
)


type Trampoline a
= Done a
| Jump (() -> Trampoline a)


evaluate : Trampoline a -> a
evaluate trampoline =
case trampoline of
Done value ->
value

Jump f ->
evaluate (f ())

0 comments on commit daeb4f4

Please sign in to comment.