Platform.md
May 18, 2018 · View on GitHub
Module Elm.Platform
Program
newtype Program flags model msg
A
Programdescribes how to manage your Elm app.You can create headless programs with the
programandprogramWithFlagsfunctions. Similar functions exist inHtmlthat let you specify a view.Honestly, it is totally normal if this seems crazy at first. The best way to understand is to work through guide.elm-lang.org. It makes way more sense in context!
Instances
Newtype (Program flags model msg) _
program
program :: forall flags model msg. { init :: Tuple model (Cmd msg), update :: msg -> model -> Tuple model (Cmd msg), subscriptions :: model -> Sub msg } -> Program flags model msg
Create a headless program. This is great if you want to use Elm as the “brain” for something else. You can still communicate with JS via ports and manage your model, you just do not have to specify a
view.Initializing a headless program from JavaScript looks like this:
var app = Elm.MyThing.worker();
programWithFlags
programWithFlags :: forall flags model msg. { init :: flags -> Tuple model (Cmd msg), update :: msg -> model -> Tuple model (Cmd msg), subscriptions :: model -> Sub msg } -> Program flags model msg
Same as
program, but you can provide flags. Initializing a headless program (with flags) from JavaScript looks like this:var app = Elm.MyThing.worker({ user: 'Tom', token: 1234 });Whatever argument you provide to
workerwill get converted to an Elm value, allowing you to configure your Elm program however you want from JavaScript!
runProgram
runProgram :: forall flags model msg. flags -> Program flags model msg -> IO Unit
In Elm, you simply assign your Program to main and it gets run. Here,
we need to explicilty run a program to turn it into an IO that you can
execute.
Eventually, this will need to take into account flags and ports, which are Elm's way of communicating with Javascript. Flags would be an argument that the Javascript side can call when starting the program. Ports would be something returned by the function that the Javascript side calls, which the Javascript side can use to subscribe to values and send values.
So, eventually this will problem look more like:
runProgram :: ∀ flags model msg. Foreign -> Program flags model msg -> IO Ports
for some definition of Ports (in fact, there's an extra level of
indirection in Elm with the .worker() call, so we could set that up as
well). And, flags will probably need a typeclass constraint in order to
support the auto-decoding that Elm does. (We can probably auto-decode
using generics).
Also, eventually this will need to take the view into account! (That
code is mostly in elm-lang/html, though some code will be needed here to
integrate with that).
Actually, I think this function will be the "internal" one, so we'll just
require The flags to be prvoided. We'll have another function that is
meant for consumption from Javascript, in which we require that the
flags have a Decode instance, which you can generate via
purescript-foreign-generics.
Task
type Task x a = ExceptT x IO a
Represents asynchronous effects that may fail. It is useful for stuff like HTTP.
For example, maybe we have a task with the type (
Task String User). This means that when we perform the task, it will either fail with aStringmessage or succeed with aUser. So this could represent a task that is asking a server for a certain user.
Implemented in terms of Purescript's IO type, with ExceptT layered on top
in order to provide for a polymorphically-typed error channel.
We use IO rather than Aff because the effects-tracking complicates the
conversion of Elm code, and Purescript is moving away from it anyway.
ProcessId
type ProcessId = Fiber (infinity :: INFINITY) Unit
Head over to the documentation for the
Processmodule for information on this. It is only defined here because it is a platform primitive.
Router
data Router appMsg selfMsg
An effect manager has access to a “router” that routes messages between the main app and your individual effect manager.
sendToApp
sendToApp :: forall x a msg. Router msg a -> msg -> Task x Unit
Send the router a message for the main loop of your app. This message will be handled by the overall
updatefunction, just like events fromHtml.
sendToSelf
sendToSelf :: forall x a msg. Router a msg -> msg -> Task x Unit
Send the router a message for your effect manager. This message will be routed to the
onSelfMsgfunction, where you can update the state of your effect manager as necessary.As an example, the effect manager for web sockets
Manager
type Manager cmd sub selfMsg state = { init :: forall appMsg. Task Never (state appMsg), onEffects :: forall appMsg. Router appMsg selfMsg -> List (cmd appMsg) -> List (sub appMsg) -> state appMsg -> Task Never (state appMsg), onSelfMsg :: forall appMsg. Router appMsg selfMsg -> selfMsg -> state appMsg -> Task Never (state appMsg), tag :: String }
This is an initial attempt to express the things which effects modules have in common.
If, in Elm, you see something like:
effect module WebSocket where { command = MyCmd, subscription = MySub }
then the thesis is that the module is defining a (hidden) type which:
- can manage commands of the
commandtype - can manage subsriptions of the
subscriptiontype - keeps some internal state without requiring the user of the module to maintain a bunch of boilerplate
- can handle internal messages without requiring the user of the module to maintain a bunch of boilerplate
So, let's try defining that class here and see how far it gets us.
We'll start by defining a record type ... this may end up wanting to be a type-class, of course, but we won't necessarily start with that.
Cmd
newtype Cmd msg
A command is a way of telling Elm, “Hey, I want you to do this thing!” So if you want to send an HTTP request, you would need to command Elm to do it. Or if you wanted to ask for geolocation, you would need to command Elm to go get it.
Every
Cmdspecifies (1) which effects you need access to and (2) the type of messages that will come back into your application.Note: Do not worry if this seems confusing at first! As with every Elm user ever, commands will make more sense as you work through the Elm Architecture Tutorial and see how they fit into a real application!
Instances
Functor Cmd
Semigroup (Cmd msg)
Monoid (Cmd msg)
command
command :: forall cmd sub appMsg selfMsg state. Functor cmd => Manager cmd sub selfMsg state -> cmd appMsg -> Cmd appMsg
command is a magical function in Elm. It gets called by effects modules,
but it isn't explicitly implemented anywhere (probably because it can't be
well-typed in Elm).
Effects modules implement "commands" as a kind of data structure to
interpret later, which, if one were starting from scratch, you might
implement in Purescript via Free (and that may be how it ought to be
done in the end, but I'm working my way into it, by sketching the surface
of the Elm API and then seeing what's needed to implement it).
The Elm implementation has a couple of interesting characterstics:
-
It is an "open" structure ... that is, the compiler does something to bring together and dispatch the commands defined by whatever effects modules get used.
-
The Elm API is such that we need to be able to batch commands from various effects modules together, in the
Cmdtype, so long as they are parameterized by the sameappMsg. So, that implies, at the very least, that there will need to be an existential element to this ... we'll need to "forget" some of our type information and yet able to "recover" it.
Now, the special role played by the command function (in Elm) is that it
takes whatever type the particular effects module uses for its commands,
and returns a generic Cmd a that can be batched with other commands from
other effects modules. So, it is basicaly our "hook" where we can
implement whatever magic is required to make that work.
At a miminum, we're going to need the effects module to provide its
Manager. Ultimatley, it may be nice for formulate this in terms of type
classes, but I think it will be easier to start by explicitly passing
records around.
Sub
newtype Sub msg
A subscription is a way of telling Elm, “Hey, let me know if anything interesting happens over there!” So if you want to listen for messages on a web socket, you would tell Elm to create a subscription. If you want to get clock ticks, you would tell Elm to subscribe to that. The cool thing here is that this means Elm manages all the details of subscriptions instead of you. So if a web socket goes down, you do not need to manually reconnect with an exponential backoff strategy, Elm does this all for you behind the scenes!
Every
Subspecifies (1) which effects you need access to and (2) the type of messages that will come back into your application.Note: Do not worry if this seems confusing at first! As with every Elm user ever, subscriptions will make more sense as you work through the Elm Architecture Tutorial and see how they fit into a real application!
Instances
Functor Sub
Semigroup (Sub msg)
Monoid (Sub msg)
subscription
subscription :: forall cmd sub appMsg selfMsg state. Functor sub => Manager cmd sub selfMsg state -> sub appMsg -> Sub appMsg
Like command, but for subscriptions.