brian-watkins/elm-procedure/1.0.0

NOTE: the latest version is 1.1.1

Procedure

Orchestrate commands, subscriptions, and tasks.

type alias Procedure e a msg =
Procedure e a msg

Represents some sequence of commands, subscriptions, or tasks that ultimately results in some value.

Execute a Procedure

run : (Msg msg -> msg) -> (a -> msg) -> Procedure Never a msg -> Cmd msg

Execute a procedure that cannot fail.

Note: The first argument tags a Procedure.Program.Msg as a Msg in your application. The second argument tags the value produced by the Procedure as a Msg in your application.

try : (Msg msg -> msg) -> (Result e a -> msg) -> Procedure e a msg -> Cmd msg

Execute a procedure that may fail.

Note: The first argument tags a Procedure.Program.Msg as a Msg in your application. The second argument tags the result of the Procedure as a Msg in your application.

Basic Procedures

provide : a -> Procedure e a msg

Generate a procedure that simply provides a value.

Procedure.provide "Hello!"
  |> Procedure.run ProcedureTagger StringTagger

This will result in StringTagger "Hello".

fetch : ((a -> msg) -> Cmd msg) -> Procedure e a msg

Generate a procedure that gets the value produced by executing some Cmd.

For example, if you wanted to have the user select a file and then convert that to a string, you could do the following:

Procedure.fetch (File.Select.file ["application/zip"])
  |> Procedure.andThen (\file -> 
    File.toString file
      |> Procedure.fromTask
  )
  |> Procedure.run ProcedureTagger StringTagger

Note that only some commands produce a value directly. To execute those commands that do not produce a value, use do.

fetchResult : ((Result e a -> msg) -> Cmd msg) -> Procedure e a msg

Generate a procedure that gets the result produced by executing some Cmd.

For example, if you wanted to make an Http request and then map the response, you could do the following:

Procedure.fetchResult (\tagger ->
  Http.get
    { url = "http://fun.com/fun.html"
    , expect = Http.expectString tagger
    }
  )
  |> Procedure.map (\words ->
    "Fun stuff: " ++ words
  )
  |> Procedure.catch (\error ->
    Procedure.provide "No Response"
  )
  |> Procedure.run ProcedureTagger StringTagger

If the Http request fails, then the result will be: No response.

collect : List (Procedure e a msg) -> Procedure e (List a) msg

Generate a procedure that collects the results of a list of procedures.

Procedure.collect
  [ Procedure.provide "One"
  , Procedure.provide "Two"
  , Procedure.provide "Three"
  ]
  |> Procedure.run ProcedureTagger ListStringTagger

Then the result would be ListStringTagger [ "One", "Two", "Three" ].

fromTask : Task e a -> Procedure e a msg

Generate a procedure that runs a task.

For example, if you wanted to have the user select a file and then convert that to a string, you could do the following:

Procedure.fetch (File.Select.file ["application/zip"])
  |> Procedure.andThen (\file ->
    File.toString file
      |> Procedure.fromTask
  )
  |> Procedure.run ProcedureTagger StringTagger

If the task fails, the procedure will break at this point, just as if Procedure.break had been used.

break : e -> Procedure e a msg

Generate a procedure that breaks out of the current procedure.

You can use this to stop a procedure early:

Procedure.fetch (File.Select.file ["text/plain"])
  |> Procedure.andThen (\file ->
    File.toString file
      |> Procedure.fromTask
  )
  |> Procedure.andThen (\text ->
    if String.length text > 100 then
      Procedure.break "File is too long!"
    else
      Procedure.provide text
  )
  |> Procedure.map (\text -> doSomethingWithTheText text)
  |> Procedure.try ProcedureTagger StringResultTagger

where StringResultTagger tags a Result String String. If the break is triggered, then the result would be Err "File is too long!".

do : Cmd msg -> Procedure Never () msg

Generate a procedure that executes a Cmd that does not produce any value directly.

Use do to execute port functions that generate Cmd values.

Procedure.do myFunPortCommand
  |> Procedure.map (\_ -> "We did it!")
  |> Procedure.run ProcedureTagger StringTagger

If you want to send a port command and expect a response later via some subscription, use a Channel.

Build a Procedure

andThen : (a -> Procedure e b msg) -> Procedure e a msg -> Procedure e b msg

Generate a new procedure based on the result of the previous procedure.

Procedure.provide "An awesome value"
  |> Procedure.andThen (\data -> 
    Procedure.provide <| data ++ "!!!"
  )
  |> Procedure.run ProcedureTagger StringTagger

Then the result would be StringTagger "An awesome value!!!".

catch : (e -> Procedure f a msg) -> Procedure e a msg -> Procedure f a msg

Generate a new procedure when some previous procedure results in an error, usually do to processing a break procedure.

For example, you could check the result of some data and then break to skip the next steps until you reach a catch step.

Procedure.fetch someCommand
  |> Procedure.andThen (\data ->
    if data.message == "Success" then
      Procedure.provide data
    else
      Procedure.break data.message
  )
  |> Procedure.andThen (\data ->
    Channel.send (somePortCommand data)
      |> Channel.receive somePortSubscription
      |> Channel.await
  )
  |> Procedure.catch (\errorData -> 
    Procedure.provide "Some default message"
  )
  |> Procedure.map (\data -> data ++ "!")
  |> Procedure.run ProcedureTagger StringTagger

If the data fetched via someCommand is deemed not successful, then the next steps will be skipped and the result of this procedure will be StringTagger "Some default message!".

Map the Output of a Procedure

map : (a -> b) -> Procedure e a msg -> Procedure e b msg

Generate a procedure that transforms the value of the previous procedure.

Procedure.collect
  [ Procedure.provide "One"
  , Procedure.provide "Two"
  , Procedure.provide "Three"
  ]
  |> Procedure.map (String.join ", ")
  |> Procedure.run ProcedureTagger StringTagger

Then the result would be StringTagger "One, Two, Three".

(a -> b -> c)
-> Procedure e a msg
-> Procedure e b msg
-> Procedure e c msg

Generate a procedure that provides a new value based on the values of two other procedures

Procedure.map2
  (\a b -> a ++ " AND " ++ b)
  (Procedure.provide "One")
  (Procedure.provide "Two")
  |> Procedure.run ProcedureTagger StringTagger

Then the result would be `StringTagger "One AND Two"

(a -> b -> c -> d)
-> Procedure e a msg
-> Procedure e b msg
-> Procedure e c msg
-> Procedure e d msg

Generate a procedure that provides a new value based on the values of three other procedures.

mapError : (e -> f) -> Procedure e a msg -> Procedure f a msg

Generate a procedure that transforms the error value of a previous procedure.

Procedure.provide "Start"
  |> Procedure.andThen (\_ -> Procedure.break "Oops")
  |> Procedure.mapError (\err -> err ++ "???")
  |> Procedure.try ProcedureTagger ResultTagger

Then the result would be Err "Oops???".

Note: Error values can be set explicitly by using break.