Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Pipeline module #12

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"exposed-modules": [
"Recursion",
"Recursion.Fold",
"Recursion.Traverse"
"Recursion.Traverse",
"Recursion.Pipeline",
"Recursion.Pipeline.Fold",
"Recursion.Pipeline.Traverse"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
Expand Down
150 changes: 150 additions & 0 deletions src/Recursion.elm
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,156 @@ for helpers that work with containers of recursive types.
@docs runRecursion


# Converting unsafe recursions

Converting an existing unsafe recursion to use `elm-safe-recursion` is very straightforward! There are only three steps:

1. Make the existing function definition into lambda function and pass it as the first argument to `runRecursion`
2. Wrap each base case of your recursion in a call to `base` and change each recursive function call to be `recurse`
3. Stitch together the `Rec` types created in your calls to `recurse` using `map`, `andThen`, and other helpers from [`Recursion.Traverse`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Traverse) and [`Recursion.Fold`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Fold)

These steps are simple enough to describe, but there might be some art involved (especially in step #3). So let's work through some examples!


## Example: Fibonacci

Note: This will not be the right™️ way to implement the calculation of the fibonacci sequence. Closed form expression and memoized algorithms exist.
What we do here is take an inefficient and unsafe version and convert to an inefficient but safe version.


### Naive Implementation

fib : Int -> Int
fib x =
case x of
0 ->
1

1 ->
1

_ ->
fib (x - 1) + fib (x - 2)


### **Step 1:** Pass our function definition to `runRecursion`

fib : Int -> Int
fib init =
runRecursion
(\x ->
case x of
0 ->
1

1 ->
1

_ ->
fib (x - 1) + fib (x - 2)
)
init


### **Step 2:** Base cases use `base` and recursive calls use `recurse`

fib : Int -> Int
fib init =
runRecursion
(\x ->
case x of
0 ->
base 1

1 ->
base 1

_ ->
recurse (x - 1) + recurse (x - 2)
)
init


### **Step 3:** Fix the types using `map` and `andThen`

Our code above will generate a compiler error that looks like this:

TYPE MISMATCH - Addition does not work with this value:

recurse (x - 1) + recurse (x - 2)
#^^^^^^^^^^^^^^#
This `recurse` call produces:

#Rec Int t t#

But (+) only works with #Int# and #Float# values.

Previously we had a recursive call to `fib` which would give us an `Int` which we could add immediately.
Now we have a `Rec` type which, similar to a `Promise` in javascript, might not actually have the value yet!

We can use `andThen` to specify that the addition be done after the result of `recurse (x - 1)` is available.

`recurse (x - 1) |> andThen (\fibX1 -> ...)`

And we'll need to do a similar thing for `recurse (x - 2)` as well.

`recurse (x - 2) |> andThen (\fibX2 -> ...)`

If we combine these together we'll have access to both `fibX1` and `fibX2` and can add them together:

fib : Int -> Int
fib init =
runRecursion
(\x ->
case x of
0 ->
base 1

1 ->
base 1

_ ->
recurse (x - 1)
|> andThen
(\fibX1 ->
recurse (x - 2)
|> map (\fibX2 -> fibX1 + fibX2)
)
)
init

🎉🎉🎉 And we're done! 🎉🎉🎉

Well not quite. As noted in the documentation above, due to some unfortunate implementation details, doing `recurse ... |> andThen ...` is not as efficient as it could be.
This is why the `recurseThen` function exists.

Let's revise our code to use `recurseThen` instead:

fib : Int -> Int
fib init =
runRecursion
(\x ->
case x of
0 ->
base 1

1 ->
base 1

_ ->
recurseThen (x - 1)
(\fibX1 ->
recurseThen (x - 2)
(\fibX2 ->
base (fibX1 + fibX2)
)
)
)
init

Not only faster, but even even a little easier on the eyes 🙃


# Example

Imagine we have a generic binary tree type that we want to write a map function for:
Expand Down
18 changes: 18 additions & 0 deletions src/Recursion/Fold.elm
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ foldList fold accum items =


{-| Fold a list of items which are recurisve types and then perform a recursive action with the result.

For a pipeable version see [`foldListPipe`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Pipeline-Fold#foldListPipe).

-}
foldListThen : (t -> a -> a) -> a -> List r -> (a -> Rec r t b) -> Rec r t b
foldListThen fold accum items after =
Expand Down Expand Up @@ -98,6 +101,9 @@ foldMapList foldMap accum items =


{-| Fold a list of items which contain recursive types and then perform a recursive action with the result.

For a pipeable version see [`foldMapListPipe`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Pipeline-Fold#foldMapListPipe).

-}
foldMapListThen : (x -> a -> Rec r t a) -> a -> List x -> (a -> Rec r t b) -> Rec r t b
foldMapListThen foldMap accum items after =
Expand Down Expand Up @@ -133,6 +139,9 @@ foldDict fold init dict =


{-| Fold a `Dict` whose values are recursive types and then perform a recursive action with the result.

For a pipeable version see [`foldDictPipe`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Pipeline-Fold#foldDictPipe).

-}
foldDictThen : (comparable -> t -> a -> a) -> a -> Dict comparable r -> (a -> Rec r t b) -> Rec r t b
foldDictThen fold init dict after =
Expand Down Expand Up @@ -181,6 +190,9 @@ foldMapDict foldMap init dict =


{-| Fold a `Dict` whose values contain recursive types and then perform a recursive action with the result.

For a pipeable version see [`foldMapDictPipe`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Pipeline-Fold#foldMapDictPipe).

-}
foldMapDictThen : (comparable -> v -> a -> Rec r t a) -> a -> Dict comparable v -> (a -> Rec r t b) -> Rec r t b
foldMapDictThen foldMap init dict after =
Expand All @@ -204,6 +216,9 @@ foldArray fold accum items =


{-| Fold an `Array` whose items are recursive types and then perform a recursive action with the result.

For a pipeable version see [`foldArrayPipe`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Pipeline-Fold#foldArrayPipe).

-}
foldArrayThen : (t -> a -> a) -> a -> Array r -> (a -> Rec r t b) -> Rec r t b
foldArrayThen fold accum items after =
Expand All @@ -218,6 +233,9 @@ foldMapArray foldMap accum items =


{-| Fold an `Array` whose items contain recursive types and then perform a recursive action with the result.

For a pipeable version see [`foldMapArrayPipe`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Pipeline-Fold#foldMapArrayPipe).

-}
foldMapArrayThen : (x -> a -> Rec r t a) -> a -> Array x -> (a -> Rec r t b) -> Rec r t b
foldMapArrayThen foldMap accum items after =
Expand Down
58 changes: 58 additions & 0 deletions src/Recursion/Pipeline.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module Recursion.Pipeline exposing
( startPipe, endPipe
, basePipe, recursePipe
, toPipeable, Pipeline
)

{-|

@docs startPipe, endPipe

@docs basePipe, recursePipe

@docs toPipeable, Pipeline

-}

import Recursion exposing (..)


{-| A type that encapuslates a recursion pipeline.
-}
type alias Pipeline a r =
(a -> r) -> r


{-| Begin a pipeline.
-}
startPipe : a -> Pipeline a r
startPipe =
(|>)


{-| Create your own pipelineable function
-}
toPipeable : ((a -> r) -> r) -> Pipeline (a -> z) r -> Pipeline z r
toPipeable thenFunc before cont =
before ((<<) cont >> thenFunc)


{-| A pipeable version of `base`
-}
basePipe : x -> Pipeline (x -> z) (Rec r t a) -> Pipeline z (Rec r t a)
basePipe x =
toPipeable ((|>) x)


{-| A pipeable version of `recurse`
-}
recursePipe : r -> Pipeline (t -> z) (Rec r t a) -> Pipeline z (Rec r t a)
recursePipe r =
toPipeable (recurseThen r)


{-| End a pipeline, recovering the `Rec` object.
-}
endPipe : Pipeline a (Rec r t a) -> Rec r t a
endPipe =
(|>) base
72 changes: 72 additions & 0 deletions src/Recursion/Pipeline/Fold.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
module Recursion.Pipeline.Fold exposing
( foldListPipe, foldMapListPipe
, foldDictPipe, foldMapDictPipe
, foldArrayPipe, foldMapArrayPipe
)

{-|


# List

@docs foldListPipe, foldMapListPipe


# Dict

@docs foldDictPipe, foldMapDictPipe


# Array

@docs foldArrayPipe, foldMapArrayPipe

-}

import Array exposing (Array)
import Dict exposing (Dict)
import Recursion exposing (..)
import Recursion.Fold exposing (..)
import Recursion.Pipeline exposing (..)


{-| Pipeable version of [`foldListThen`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Fold#foldListThen).
-}
foldListPipe : (t -> b -> b) -> b -> List r -> Pipeline (b -> z) (Rec r t a) -> Pipeline z (Rec r t a)
foldListPipe fold accum items =
toPipeable (foldListThen fold accum items)


{-| Pipeable version of [`foldMapListThen`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Fold#foldMapListThen).
-}
foldMapListPipe : (x -> b -> Rec r t b) -> b -> List x -> Pipeline (b -> z) (Rec r t a) -> Pipeline z (Rec r t a)
foldMapListPipe foldMap accum items =
toPipeable (foldMapListThen foldMap accum items)


{-| Pipeable version of [`foldDictThen`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Fold#foldDictThen).
-}
foldDictPipe : (comparable -> t -> b -> b) -> b -> Dict comparable r -> Pipeline (b -> z) (Rec r t a) -> Pipeline z (Rec r t a)
foldDictPipe fold init dict =
toPipeable (foldDictThen fold init dict)


{-| Pipeable version of [`foldMapDictThen`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Fold#foldMapDictThen).
-}
foldMapDictPipe : (comparable -> v -> b -> Rec r t b) -> b -> Dict comparable v -> Pipeline (b -> z) (Rec r t a) -> Pipeline z (Rec r t a)
foldMapDictPipe foldMap init dict =
toPipeable (foldMapDictThen foldMap init dict)


{-| Pipeable version of [`foldArrayThen`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Fold#foldArrayThen).
-}
foldArrayPipe : (t -> b -> b) -> b -> Array r -> Pipeline (b -> z) (Rec r t a) -> Pipeline z (Rec r t a)
foldArrayPipe fold accum items =
toPipeable (foldArrayThen fold accum items)


{-| Pipeable version of [`foldMapArrayThen`](https://package.elm-lang.org/packages/micahhahn/elm-safe-recursion/2.0.0/Recursion-Fold#foldMapArrayThen).
-}
foldMapArrayPipe : (x -> b -> Rec r t b) -> b -> Array x -> Pipeline (b -> z) (Rec r t a) -> Pipeline z (Rec r t a)
foldMapArrayPipe foldMap accum items =
toPipeable (foldMapArrayThen foldMap accum items)
Loading