Talking about Effects

413 views
Skip to first unread message

Evan Czaplicki

unread,
May 2, 2014, 8:55:12 PM5/2/14
to elm-d...@googlegroups.com
I'm just writing this so the idea gets put down :)

I'd like to argue that "monads are mostly useful for reifying effects" is so true that it just makes sense to talk about effects and skip the idea of monads entirely.

There are many useful things that have monadic structure:
  • State
  • IO
  • HTTP
  • database access
  • error reporting (Maybe, Either, Result)
  • backtracking (Lists)
  • Context sensitive parsing (Parsec)
I'd argue that all of these deal with effects in some nontrivial way, so much so that talking about effects is general enough to capture the practical essence of the pattern. To generalize the pattern used in all of these, we have a way to thread information:

andThen : effect a -> (a -> effect b) -> effect b

It may be true that this pattern appears in other contexts that could not really be considered effects, but I can't think of any I have ever used. When presenting an idea to a student, the idea is to get them to understand quickly and become proficient. Making sure you can talk about this pattern in a fully general way is really not important, unless that is their goal of course. My claim is that when someone wants to learn about State in Elm, they want to learn about State! Perhaps noticing that it follows the same pattern as all other effects is helpful, but the fact that there is an even more general way to think about State that covers things besides effects (monads) is not an essential detail.

We are working with explicit effects. We have data structures that contain continuations, telling us "here's what you do next". I don't think anything a typical Elm programmer wants to do requires being more generic than that.

So I think we should try to be as precise and specific as possible when talking about these things. If a concept is not needed in an explanation, it shouldn't be in the explanation. I really wish the idea of Skillful Means was at all known in the US. It's exactly this!

I feel like this is just a fact about teaching and conversation and story telling. If you add a bunch of superfluous stuff, it's just not as good. It might be "more complete" or "more true" if you know exactly how long each blade of grass was at the soccer game, but you probably want to focus more on the score or where the ball is.

Eitan Chatav

unread,
May 2, 2014, 9:22:36 PM5/2/14
to elm-d...@googlegroups.com
Different people learn in different ways and often find different kinds of conceptualizations to be the more natural one. I think you may be confusing what's most natural for you with what's most natural for everyone. What is the definition of "effect"? I really like the notion that andThen has a continuation; I never noticed that before. Here's three different conceptualizations:

Effects - this is the most concrete but it seems vague to me
Domain specific imperative languages - i.e. programmable semicolon
Algebraic operations and laws - this is the most abstract and precise

They also don't entirely overlap. Any particular monad has more operations than just bind and return like get and put for State and that's what makes it a DSL for a specific purpose, or governs what we can do with that effect.

Evan Czaplicki

unread,
May 2, 2014, 9:40:57 PM5/2/14
to elm-d...@googlegroups.com
I think the fact that it's so hard to recognize the continuation in there is a testament to how things have gone wrong in the Haskell community. That is such a key fact, and it took me like three years to deduce it for myself. I agree that you can frame it different ways for different people, my contention is that the set of people who will benefit from calling the general pattern an Effect is pretty much all programmers and the set of people that benefit from monad or algebraic operations and laws is a tiny subset of that population.

I also think that "programmable semicolon" is the same as effects. Statements in an imperative language are a sequence of effects that you need to perform in order.
 
They also don't entirely overlap. Any particular monad has more operations than just bind and return like get and put for State and that's what makes it a DSL for a specific purpose, or governs what we can do with that effect.

I think this really illustrates why the term "the IO monad" is destructive for understanding what a monad is! A monad has exactly two operators:


class Monad m where
  return :: a -> m a
  (>>=)  :: m a -> (a -> m b) -> m b

It is a general pattern. So IO has monadic structure, meaning these two operations can be defined for IO. But that does not capture the essential details of IO! A monad is a set of algebraic properties that apply or don't apply to specific things in the world.

To say "Any particular monad has more operations than just bind and return" I think is key to why it's hard to understand what a monad is. It's a type error. (IO : reified effect) and (Monad : algebraic pattern)

Evan Czaplicki

unread,
May 2, 2014, 9:43:09 PM5/2/14
to elm-d...@googlegroups.com
I think part of what made me think this way was reading this paper on Idris. The literature on effect systems seems to have a much more lucid way of talking about effects in a purely functional setting (perhaps unsurprisingly :P)

Evan Czaplicki

unread,
May 2, 2014, 10:00:27 PM5/2/14
to elm-d...@googlegroups.com
Also, I really hope I'm not sounding like a jerk! I am weirdly animated about this today, and I feel like I am sharing my opinions with more vehemence than normal for internet writing.

I think I'm just excited about framing things in this particular way and want to describe it really clearly and argue for this viewpoint.

Brian Slesinsky

unread,
May 2, 2014, 10:26:28 PM5/2/14
to elm-d...@googlegroups.com
Maybe better to figure out what we should say rather than what we shouldn't say? If the terminology is useful then it'll get picked up.

In imperative languages, instead of "reified effects" we would talk about commands (or actions) and callbacks or event handlers. I don't think these are inappropriate for Elm, although perhaps "decider function" or "decider" might be a better name for a function that returns the next command, since it's more restrictive than an event handler.

"Whenever a you have a command that outputs a value, you can append a decider function that chooses the next command. The result is is a larger command that makes a decision in the middle. Entire programs can be constructed this way, out of a set of built-in commands and deciders."

"Foldp constructs a state machine, given a stream of inputs and a decider function that returns the next state. Its output is a stream of states."

- Brian

Eitan Chatav

unread,
May 2, 2014, 10:53:02 PM5/2/14
to elm-d...@googlegroups.com
Apparently in F#, they're called "computation expressions" or "workflows". I kind of like "workflow" as it captures the imperative DSL concept.


--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/j9Da2UIA5xo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Evan Czaplicki

unread,
May 2, 2014, 11:29:04 PM5/2/14
to elm-d...@googlegroups.com
Perhaps the big question is essentially: What is a value of type (State Int String)?

It's weirdly hard to answer! So I went on #idris and asked what they say. Someone suggested that it is called stateful computation! Holy crap, that's literally exactly what it is. It's a computation that you can run. It has state.

Hmm, it seems harder for (IO String). Perhaps it could be called an effectful computation? A (Http Json.Value) could be called an effectful computation as well.

I am sort of into "reified computation" at the moment. That computation may perform effects when it is actually run, but maybe not. So (State Int String) is a reified computation, but because it has no effects, it can be run in a totally pure setting.

I don't think "reified computation" is so accessible, but it seems to capture the key idea very precisely.

Perhaps "explicit effect" is an okay way to describe things to someone totally new to Elm, but "reified computation" is the more nuanced version that explains exactly what's going on.

Max Goldstein

unread,
May 2, 2014, 11:51:42 PM5/2/14
to elm-d...@googlegroups.com
No Evan, I don't think you're sounding like a jerk. Not only are you not attacking an individual, you're not attacking any individual's ideas. Monads are embraced by a large enough community that no individual "owns" them. (Though I do appreciate that you're sensitive to it, and I want to thank you for your apology on that PR recently when you realized you came off as defensive.)

I wonder how much of the rage against monads is based on not wanting what we can't have, i.e. performant monadic signals? With regards to the Idris paper, what are your thoughts on dependent types?

As we saw in the HTTP thread, I am against callbacks, and promises which are apparently very similar, because they force the programmer to consider multiple moments in time. I do not fully understand the details here an I am open to changing my mind as I learn more. At the moment, binding (in the monadic sense) effect handlers while let the data flow through signals looks like a good compromise. So you could make another HTTP request on success or fail; bound functions serve only when you must stay within the monad. I want to escape the monad with pure data in hand, and return to the signal graph as soon as possible.

But to walk back my bold claims and take the idea of Skillful Means meta, I don't fully understand where you're going with this Evan, but I trust you.

The simplest monad is probably Maybe, and I think understand the functor/applicative/monad progression for it (but not in those terms) is straightforward enough to be useful. Optional values are everywhere and frequently done poorly. Trying to address Maybes with more general effect infrastructure is a tricky problem, but the discussion stemming from get methods for Dict show there is interest in doing Maybe well. When we added Arrays, the sentiment was against type annotation sugar, but I would entertain having Type? mean Maybe Type. That said, Maybes aren't effectful.

The term "reify" may not be as unheard-of as you think.

Evan Czaplicki

unread,
May 2, 2014, 11:51:36 PM5/2/14
to elm-d...@googlegroups.com
I'm going to try to make up a plausible description for an IO library:

"""
Most languages allow arbitrary side-effects, so any function you write may be reading a file or sending HTTP requests. To avoid the pitfalls that comes with this, Elm uses explicit effects. This means building up a data structure that represents the computation you want to run. The trick is that we don't actually run the computation immediately!  Instead, we build up a data structure with type (IO Int) which specifies exactly which effects we'd like to perform at some point. To run this computation, we give it to Elm's runtime system, which will perform the specified IO operations and give us back an Int.

But why go through the trouble of making effects explicit? Seems the same whether the code runs it or the runtime runs it! The easiest example is the Elm Debugger. If Elm code could write to disk at any moment, traveling back and forth through time could badly corrupt your files! With explicit effects, the computation is run by the runtime system, so the runtime system can just ignore all effectful computations when you are in debug mode, making it safe to travel time!
"""

Not sure how successful that was, but hopefully that's a good attempt for addressing how we should talk about these things :)

Evan Czaplicki

unread,
May 3, 2014, 12:12:38 AM5/3/14
to elm-d...@googlegroups.com
Holy moly, I love that link you sent about Gang of Four's Command pattern. That's exactly the same thing and a good source of inspiration for terminology! I like that reify = thingify :)

I don't want to address HTTP here, but I get your viewpoint. It seems like FRP can do it all, but I think you start making very serious trade offs that get you to a bad place really quickly.

I think my whole "monads = containers" thing was wrong. Like, it's a type error and we should think of things differently. Monads are properties and containers are things.

I'd present Maybe like this: Maybe can be used to represent computations that may fail. With the andThen function, we can chain together these computations:

andThen : Maybe a -> (a -> Maybe b) -> Maybe b

I don't think we need to say more than that really.

Maybe has never been my favorite name. I remember a fun post about alternatives ages ago, so perhaps we should revisit this. With the idea of making "computations that may fail" a proper thing (rather than Maybe and Either) perhaps we will have some fresh ideas of how to make things more consistent.


--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Max Goldstein

unread,
May 3, 2014, 12:35:52 AM5/3/14
to elm-d...@googlegroups.com
Brainstorming: reify, thingify, explicit, concrete, tangible, manifested, substantial, material, made real, holdable, incarnated. Not the best choices, but "tangible effects" could work.

I liked the name MayFail as a complement to Maybe. If both are up for renaming, we should consider giving them related names, even if not those names.

Dobes Vandermeer

unread,
May 3, 2014, 12:59:21 AM5/3/14
to elm-d...@googlegroups.com
Hi Evan,

Monads are necessarily about effects - in functional programming ala Haskell they seem mainly to be about encoding procedures or imperative code into a purely functional language.  Without monads and "do notation" its a pain to write a step-by-step process in Haskell.  This is my impression, anyway.

Elm's signals don't quite solve this problem in general, but it does solve it nicely when it does.  Instead of returning a bunch of steps requrie to mutate the display, just return some representation of the new display and hide the procedure for updating the display in the runtime.  Very nice and convenient.

However, not every step-by-step process is supported directly by the runtime.  For example if you want to fetch something from a web service, make a change to it, and send it back this doesn't encode well into signals at all currently.  Or so it seems to me.

In this case Haskell-style monads would be a more flexible solution that makes it possible to chain some steps together with clear dependencies - do this, send the result to that function, now do that and send the result to this other function.  It's a lot like callbacks, really, when you are writing code this way.

I think the term "monad" is sticking in Haskell because it's general enough to capture all the uses of monad-related code and syntax.  Once you try to pin it down to something more specific - procedures, I/O, iteration, optional values - you'll get some new case that comes along and breaks out of that category.  Someone had to name that type class for this stuff; monad must have seemed the best choice.  Once the monad type class is there people can write code that applies to all the monads out there, and the names won't throw people off.  They could have called it foobarg or understuffles or shufflywuffly but it needs a name of some sort.

Now, the tricky thing about monads is that it's such an abstract concept, it's hard to explain.  Unlike ideas like lists, sequences, arrays, dictionaries, and so on there's no real-world counterpart for us to attach to ... it's a tough situation.  Perhaps some great innovator will find the escape hatch on that one.







--

Antti Rasinen

unread,
May 3, 2014, 1:51:26 AM5/3/14
to elm-d...@googlegroups.com
I'm going to try to make up a plausible description for an IO library:

"""
Most languages allow arbitrary side-effects, so any function you write may be reading a file or sending HTTP requests. To avoid the pitfalls that comes with this, Elm uses explicit effects. This means building up a data structure that represents the computation you want to run. The trick is that we don't actually run the computation immediately!  Instead, we build up a data structure with type (IO Int) which specifies exactly which effects we'd like to perform at some point. To run this computation, we give it to Elm's runtime system, which will perform the specified IO operations and give us back an Int.

Oh. So that's what it does. That might be a very good way to explain it. Since I'm no Haskellist, I'd like to verify I've understood it. I have a question, I need to go on a tangent here for a second.

Last Autumn I took the Coursera course on Reactive Programming. I had a lot of trouble with Futures and Promises, because I got zero mileage out of Meijer's mailbox metaphor. At one point I realized a pneumatic mail system worked better for me, after which I could extract a DAG out of it. Every computation has inputs and (usually) one output. You can build a dependency graph out of these. Incoming edges are input, the function/operation lives in the vertex and the outgoing edge is the output.

In an synchronous computation, the specification of the structure of the graph and the traversal of that graph (i.e. the execution of the computation) happen simultaneously. When you reach a vertex, you have all the data you need to proceed next node.

In an asynchronous computation, whether callbacks or futures, you define the structure ahead of execution time. There exists a fringe of unevaluated operations, whose leading front is the "definition front" and whose trailing front is the "execution front." The existence of this fringe area is what helped me finally understand Futures and Promises.

Finally, the question. As I read your description of the IO mon^H^H^Hlibrary, I see a the very same thing! There's a DAG, although with a different shape. The async DAG has many inputs, which ultimately converge on a single vertex. With the IO monad, the DAG looks more like a chain, except there may be an edge out of each vertex leading to an error vertex? Is that accurate? It feels it'd be true at least for the Maybe monad. Is it true for all monads, I wonder?

—Antti, confused but hopeful

Jeff Smits

unread,
May 3, 2014, 7:48:29 AM5/3/14
to elm-discuss
What is a value of type (State Int String)?
I was recently reading another Haskell tutorial for beginners (I find that I usually learn new insights from the different explanations), and there was a really simple example about when to introduce State in Haskell. I can't find it right now but it was transforming a pure function to interleaving state explicitly as an extra argument and return value, then switching to using State.
So now I read (State Int String) as (Int -> (Int, String)).

Another tangent:
Max mentioned the "having Type? mean Maybe Type" again and together with talking about effects, that made be think of a nice feature I'd like to see in functional programming some day: distinguishing total functions from partial functions (e.g. -?> for a partial function) and facilities to match on the failure of a partial function. The reason I want this is that partial functions are already around, but they're not nice. So we use Maybe or others to explicitly express this effect that is in the system but cannot be handled.

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Brian Slesinsky

unread,
May 3, 2014, 2:32:28 PM5/3/14
to elm-d...@googlegroups.com
On Fri, May 2, 2014 at 8:51 PM, Evan Czaplicki <eva...@gmail.com> wrote:
I'm going to try to make up a plausible description for an IO library:

"""
Most languages allow arbitrary side-effects, so any function you write may be reading a file or sending HTTP requests. To avoid the pitfalls that comes with this, Elm uses explicit effects. This means building up a data structure that represents the computation you want to run. The trick is that we don't actually run the computation immediately!  Instead, we build up a data structure with type (IO Int) which specifies exactly which effects we'd like to perform at some point. To run this computation, we give it to Elm's runtime system, which will perform the specified IO operations and give us back an Int.

But why go through the trouble of making effects explicit? Seems the same whether the code runs it or the runtime runs it! The easiest example is the Elm Debugger. If Elm code could write to disk at any moment, traveling back and forth through time could badly corrupt your files! With explicit effects, the computation is run by the runtime system, so the runtime system can just ignore all effectful computations when you are in debug mode, making it safe to travel time!
"""

How about this?

 """
A program running in a web browser needs a way to send HTTP requests to the server and get back a response. In Elm, we do this by creating a command that describes what we want to do. A command has a type such as (Command Int), where Int is the output of the command. To run a command, we give it to Elm's runtime system, which will execute it and give us back an Int.

How do we create commands? Elm has many built-in functions to create commands that perform basic operations. [Give example] These are the basic building blocks for interacting with the outside world.

We can also build our own, larger commands by combining the basic commands that Elm gives us. A command may consist of many intermediate steps and call functions while it's running to decide what to do next. We call these functions deciders, since they decide what to do but don't actually do it. Deciders have a type such as (String -> Command Int), where String is the input and Int is the output of whichever command it decides on.

Why do we have deciders instead of event handlers like in other browser languages? One example is the Elm debugger. The debugger can call a decider as many times as it likes without executing the commands it returns. This makes it easy to simulate traveling back and forth in time without sending HTTP requests to the server, which might overwrite or delete data.
"""

I'm wondering how time-travel debugging with HTTP requests will work in practice, though. For a video game that represents game inputs using signals, the game controls are fixed and we can just pretend that the user is button-mashing and playing badly. But for a server this doesn't seem like enough. If we go back in time and change something, this will result in a different request to the server, so the server should send back a different response. Playback using canned responses might not make any sense; we're not doing a real "what-if" simulation.

For example, suppose the user fills out a form and hits "save". This sends a request to the server and returns a response which is shown on the confirmation screen. On playback, if we fill out the form differently, we'll always get the same confirmation even if it makes no sense. There would be similar problems with a multiuser video game that uses a server: you shoot someone and they don't die, or you miss and they do die, or more likely the server moves you around the game ignoring your input entirely. We can't plausibly simulate what happens without simulating the server's decisions as well.

When writing tests, there are lots of techniques for substituting an alternate implementation of an external API: stubs, mocks, fakes, and so on. It seems like we will need similar flexibility for interactive debugging.

In the case of stateless HTTP requests (most get requests, other than jsonp) it makes more sense to actually send the request when doing time-travel debugging, unless the response is already in the browser's cache.

- Brian

Evan Czaplicki

unread,
May 3, 2014, 2:43:46 PM5/3/14
to elm-d...@googlegroups.com
I am starting to like Command more and more. It feels much more concrete than computation, though perhaps not strictly better.

And now that I know that the Command Pattern is a thing, with quite a lot of practical examples for an OO person, it seems pretty non-crazy to go with that. Instead of do-notation, perhaps there would be command-syntax :)

(State Int String) is a command. You run the command, resulting in a String. (IO Int) is a command. You run the command, some effects are performed and you get an Int. We build up commands from other commands. Overall, this seems pretty solid!

I should mention that I'm not into the decider part though. I think folks are familiar enough with callbacks by now, that it's okay to call it that if it really does need to be given an explicit name.


--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Brian Slesinsky

unread,
May 3, 2014, 3:03:31 PM5/3/14
to elm-d...@googlegroups.com
On Sat, May 3, 2014 at 11:43 AM, Evan Czaplicki <eva...@gmail.com> wrote:

I should mention that I'm not into the decider part though. I think folks are familiar enough with callbacks by now, that it's okay to call it that if it really does need to be given an explicit name.

Sure, if we're avoiding new terms (generally a good idea), we could just say that HTTP callbacks in Elm return commands.

- Brian

John Nilsson

unread,
May 3, 2014, 3:57:52 PM5/3/14
to elm-d...@googlegroups.com
There is also the CQRS and Domain Driven Design if you need a more recent example of similar ideas that should be familiar to most OOP developers by now. Greg Young has also done some PR for using functional programming in that setting. So there is a seed to build on.

F.ex a state machine responding to events and producing commands is called a Saga in that community.

BR
John


lördagen den 3:e maj 2014 skrev Evan Czaplicki <eva...@gmail.com>:
I am starting to like Command more and more. It feels much more concrete than computation, though perhaps not strictly better.

And now that I know that the Command Pattern is a thing, with quite a lot of practical examples for an OO person, it seems pretty non-crazy to go with that. Instead of do-notation, perhaps there would be command-syntax :)

(State Int String) is a command. You run the command, resulting in a String. (IO Int) is a command. You run the command, some effects are performed and you get an Int. We build up commands from other commands. Overall, this seems pretty solid!

I should mention that I'm not into the decider part though. I think folks are familiar enough with callbacks by now, that it's okay to call it that if it really does need to be given an explicit name.


On Sat, May 3, 2014 at 11:32 AM, Brian Slesinsky <br...@slesinsky.org> wrote:

On Fri, May 2, 2014 at 8:51 PM, Evan Czaplicki <eva...@gmail.com> wrote:
I'm going to try to make up a plausible description for an IO library:

"""
Most languages allow arbitrary side-effects, so any function you write may be reading a file or sending HTTP requests. To avoid the pitfalls that comes with this, Elm uses explicit effects. This means building up a data structure that represents the computation you want to run. The trick is that we don't actually run the computation immediately!  Instead, we build up a data structure with type (IO Int) which specifies exactly which effects we'd like to perform at some point. To run this computation, we give it to Elm's runtime system, which will perform the specified IO operations and give us back an Int.

But why go through the trouble of making effects explicit? Seems the same whether the code runs it or the runtime runs it! The easiest example is the Elm Debugger. If Elm code could write to disk at any moment, traveling back and forth through time could badly corrupt your files! With explicit effects, the computation is run by the runtime system, so the runtime system can just ignore all effectful computations when you are in debug mode, making it safe to travel time!
"""

How about this?

 """
A program running in a web browser needs a way to send HTTP requests to the server and get back a response. In Elm, we do this by creating a command that describes what we want to do. A command has a type such as (Command Int), where Int is the output of the command. To run a command, we give it to Elm's runtime system, which will execute it and give us back an Int.

How do we create commands? Elm has many built-in functions to create commands that perform basic operations. [Give example] These are the basic building blocks for interacting with the outside world.

We can also build our own, larger commands by combining the basic commands that Elm gives us. A command may consist of many intermediate steps and call functions while it's running to decide what to do next. We call these functions deciders, since they decide what to do but don't actually do it. Deciders have a type such as (String -> Command Int), where String is the input and Int is the output of whichever command it decides on.

Why do we have deciders instead of event handlers like in other browser languages? One example is the Elm debugger. The debugger can call a decider as many times as it likes without executing the commands it returns. This makes it easy to simulate traveling back and forth in time without sending HTTP requests to the server, which might overwrite or delete data.
"""

I'm wondering how time-travel debugging with HTTP requests will work in practice, though. For a video game that represents game inputs using signals, the game controls are fixed and we can just pretend that the user is button-mashing and playing badly. But for a server this doesn't seem like enough. If we go back in time and change something, this will result in a different request to the server, so the server should send back a different response. Playback using canned responses might not make any sense; we're not doing a real "what-if" simulation.

For example, suppose the user fills out a form and hits "save". This sends a request to the server and returns a response which is shown on the confirmation screen. On playback, if we fill out the form differently, we'll always get the same confirmation even if it makes no sense. There would be similar problems with a multiuser video game that uses a server: you shoot someone and they don't die, or you miss and they do die, or more likely the server moves you around the game ignoring your input entirely. We can't plausibly simulate what happens without simulating the server's decisions as well.

When writing tests, there are l

--

Max Goldstein

unread,
May 3, 2014, 9:04:12 PM5/3/14
to elm-d...@googlegroups.com
Why do we have deciders instead of event handlers like in other browser languages?
I'm afraid you're going to have to spell out the exact difference between event handlers and deciders/bound functions, probably with type signatures.

I like the idea that a Command a is going to do some (effectful) work and then return an a. I'm afraid I'm going to use HTTP as an example again while I think through this. If our request failed, our bound handler needs to take the error and create another HTTP. It can create and send another request, as with this protocol, or it produce a default value. The name return actually jives with the C-family meaning: a conditional return statement not at the end of a function. So if we think of Http.return "foo" as as a command/effect/computation that returns "foo", that's okay, since it's just the trivial case. (I am assuming this would be lifted, so there would be a "foo" event on the downstream node, which was hoping for a string from a remote server that proved unreachable.) So far, this looks promising. But I've lost all ability to reason about the effects a command/monad may have! Does this HTTP command-monad execute an HTTP request or not? I have no idea. And I guess the big question is: do I need to?

One could also image skip : Command a which means "I didn't get a usable value from this effect so don't propagate any event", as opposed to return : a -> Command a ("use this default") and whatever specialized methods there are to introduce this particular command (e.g. send this HTTP request).

If some of that didn't make sense I'm happy to try to clarify.

Brian Slesinsky

unread,
May 3, 2014, 9:26:55 PM5/3/14
to elm-d...@googlegroups.com
On Sat, May 3, 2014 at 6:04 PM, Max Goldstein <maxgol...@gmail.com> wrote:
 
But I've lost all ability to reason about the effects a command/monad may have! Does this HTTP command-monad execute an HTTP request or not? I have no idea. And I guess the big question is: do I need to?

If we support composition, a Command could perform any number of HTTP requests and possibly other things if it becomes a grab-bag like the IO type in Haskell. We're essentially forking a task that runs in parallel to the main UI thread.

What sort of analysis would you want to do? I suppose a Command could have a reflection API to tell you which primitive commands it depends on (whether it was even linked against the HTTP request command), but not whether it will actually call them.

It seems like there's a tension between having a simple API that supports encapsulation and interchangeable parts versus exposure of the internals so you can do analysis.

- Brian

Evan Czaplicki

unread,
May 3, 2014, 10:21:01 PM5/3/14
to elm-d...@googlegroups.com
I don't think there should be a concrete Command type that can do lots of things. "command" would be the general name for a computation we're going to run. It may or may not have effects. State does not have effects, but it is a "command" in that it is a computation we are going to run.

So the Http library or IO library would be separate things. One can do HTTP requests, the other can work with stdin stdout, and stderr. That's it. If we want to read files, you'd create a separate File library with File commands.

For the sake of keeping things simple in this thread, you could put a bunch of different commands together with "nested commands" like this: Http (IO (File String))

I'm sure we can make it nicer to work with "nested commands" (drawing inspiration from monad transformers)

Does that make sense? I have a reasonably clear idea of how I want this to look by now, so I can write it up in a more clear way at some point.


Brian Slesinsky

unread,
May 3, 2014, 11:30:06 PM5/3/14
to elm-d...@googlegroups.com


On Saturday, May 3, 2014 7:21:01 PM UTC-7, Evan wrote:
I don't think there should be a concrete Command type that can do lots of things. "command" would be the general name for a computation we're going to run. It may or may not have effects. State does not have effects, but it is a "command" in that it is a computation we are going to run.

So the Http library or IO library would be separate things. One can do HTTP requests, the other can work with stdin stdout, and stderr. That's it. If we want to read files, you'd create a separate File library with File commands.

I'm curious: is File a theoretical example, or is this something you're planning on adding? Which JavaScript API would it use in a browser?

For the sake of keeping things simple in this thread, you could put a bunch of different commands together with "nested commands" like this: Http (IO (File String))

Perhaps I'm thinking too imperatively here, but this seems a bit weird to me since I'd have no notion of which way I'd want to nest the types, or even why I should modify a bunch of function signatures to call a new type of command. What does the extra structure give us?

- Brian

Evan Czaplicki

unread,
May 3, 2014, 11:37:18 PM5/3/14
to elm-d...@googlegroups.com
Yeah, File is just for example. It would make more sense in Idris or Haskell.

Are you familiar with monad transformers? This is a way to use State and Error together, for example. The order of nesting is somewhat arbitrary, but the point is that you can see in the types exactly what kind of effects will be used. This paper gets at all of this and shows a nice way to do it if you have dependent types, but it may be best to start with simpler examples of monad transformers.

I can imagine have a general purpose thing that does everything, but I think that's out of the scope of what would be happening any time soon in Elm. Max New may have thoughts on this though.


Dylan Sale

unread,
May 4, 2014, 12:48:33 AM5/4/14
to elm-d...@googlegroups.com

I like purescripts's effects. The effects that are used in a do block are defined in the record type output by the do block. Its similar to your nested types example I think.

http://www.purescript.org/posts/Eff-Monad/

JavaScript ffi is done by wrapping a JavaScript function in an effect type. You can add and remove effects (ie using throw would add an error effect, then wrapping that in a catch will remove the error effect allowing you to use runPure on the result (it no longer has any effects).

Not sure if this is relevant!

Dylan Sale

Brian Slesinsky

unread,
May 4, 2014, 2:27:49 AM5/4/14
to elm-d...@googlegroups.com
On Sat, May 3, 2014 at 8:37 PM, Evan Czaplicki <eva...@gmail.com> wrote:
Yeah, File is just for example. It would make more sense in Idris or Haskell.

Are you familiar with monad transformers? This is a way to use State and Error together, for example. The order of nesting is somewhat arbitrary, but the point is that you can see in the types exactly what kind of effects will be used. This paper gets at all of this and shows a nice way to do it if you have dependent types, but it may be best to start with simpler examples of monad transformers.

Yes, I've heard of monad transformers and skimmed a bit, but I don't yet understand the motivation enough to study them in detail.

In this paper as well, they write that there is "a temptation to build a program around a single general-purpose monad," and I don't yet understand why they want to resist that temptation. It seems like the simplest solution, at least for commands that are built into the runtime. If all commands have the same type constructor then you don't need type conversions. What could be simpler?

I'm reminded of checked exceptions in Java where distinguishing between methods that may fail and those that always succeed seems important, but putting extra precision in the type system about how a method might fail doesn't seem to have made the language any easier to use.

- Brian

Jeff Smits

unread,
May 4, 2014, 8:45:01 AM5/4/14
to elm-discuss
I'm not a big fan of monad transformers. But I'm not very familiar so my opinion is based more on the experience that it sounded complex. When I found out that you're just combining the functionality of different monads, that was a bit of an anticlimax.
What I like better is how Effect systems, like the one in Koka, combine different effects simply using records.

Evan Czaplicki

unread,
May 4, 2014, 12:42:59 PM5/4/14
to elm-d...@googlegroups.com
Yeah, it appears that PureScript has taken this approach from Idris without bringing along full dependent types. If that is in fact possible, that is very very much what I'd like to do. Again, this paper lays it out the big picture relatively clearly, but I was unable to figure out how important dependent types were.

Also, the PureScript record system looks oddly familiar :P But seeing their { x:Int, y:Int | r } syntax makes me feel like I got the order wrong in Elm. It seems way clearer to me in this order. I'll start a thread about "is it okay to change this?"

Evan Czaplicki

unread,
May 4, 2014, 12:44:27 PM5/4/14
to elm-d...@googlegroups.com
Also, yeah, let's not say monad transformers here. It is nested effects or nested commands. I think that also fully captures the problem with them, which is, what order do I nest in? And that you sometimes have to reach 1 or 2 or 3 levels deep to get the effect you want.

Max New

unread,
May 4, 2014, 6:49:45 PM5/4/14
to elm-d...@googlegroups.com


On Saturday, May 3, 2014 10:37:18 PM UTC-5, Evan wrote:
Yeah, File is just for example. It would make more sense in Idris or Haskell.

Are you familiar with monad transformers? This is a way to use State and Error together, for example. The order of nesting is somewhat arbitrary, but the point is that you can see in the types exactly what kind of effects will be used. This paper gets at all of this and shows a nice way to do it if you have dependent types, but it may be best to start with simpler examples of monad transformers.

I can imagine have a general purpose thing that does everything, but I think that's out of the scope of what would be happening any time soon in Elm. Max New may have thoughts on this though.
 

On Sat, May 3, 2014 at 8:30 PM, Brian Slesinsky <bsles...@gmail.com> wrote:


On Saturday, May 3, 2014 7:21:01 PM UTC-7, Evan wrote:
I don't think there should be a concrete Command type that can do lots of things. "command" would be the general name for a computation we're going to run. It may or may not have effects. State does not have effects, but it is a "command" in that it is a computation we are going to run.

So the Http library or IO library would be separate things. One can do HTTP requests, the other can work with stdin stdout, and stderr. That's it. If we want to read files, you'd create a separate File library with File commands.

I'm curious: is File a theoretical example, or is this something you're planning on adding? Which JavaScript API would it use in a browser?

For the sake of keeping things simple in this thread, you could put a bunch of different commands together with "nested commands" like this: Http (IO (File String))

Perhaps I'm thinking too imperatively here, but this seems a bit weird to me since I'd have no notion of which way I'd want to nest the types, or even why I should modify a bunch of function signatures to call a new type of command. What does the extra structure give us?

Extra structure means you can make more distinctions. If there's only one command type I don't know if  a `Command a` is going to write to disk or display something to the user or make an HTTP request. But this poses a problem when I want to change use my IO as an HTTP (IO a) and now I need to rewrite >>= as well (Haskell uses typeclasses to reduce the boilerplate).

The order usually does not matter unless you have control flow effects like Maybe/Either/List/Continuation combined with linear effects like State.

Brian Slesinsky

unread,
May 4, 2014, 7:45:57 PM5/4/14
to elm-d...@googlegroups.com
On Sun, May 4, 2014 at 3:49 PM, Max New <max...@gmail.com> wrote:
On Sat, May 3, 2014 at 8:30 PM, Brian Slesinsky <bsles...@gmail.com> wrote:

Perhaps I'm thinking too imperatively here, but this seems a bit weird to me since I'd have no notion of which way I'd want to nest the types, or even why I should modify a bunch of function signatures to call a new type of command. What does the extra structure give us?

Extra structure means you can make more distinctions. If there's only one command type I don't know if  a `Command a` is going to write to disk or display something to the user or make an HTTP request.

True, but why do you care? The only thing you can do with a command is return it to the runtime system so it will be executed.

But this poses a problem when I want to change use my IO as an HTTP (IO a) and now I need to rewrite >>= as well (Haskell uses typeclasses to reduce the boilerplate).

If every command were already of type Command, you could already freely intermix commands. You wouldn't need to rewrite the >>= function (which isn't possible anyway because these are all effects handled by the runtime). It seems like rewriting >>= is an artificial problem introduced by trying to make a type distinction in the first place. Maybe it's needed in Haskell where there are lots of different monads, but it can be avoided in a new language.

Similarly, in Elm, all Elements are the same type, regardless of what they look like. In an older language, you might have multiple widget systems and need to convert between them. (In some older languages there isn't even agreement on a string type.)

- Brian

Max New

unread,
May 4, 2014, 8:29:16 PM5/4/14
to elm-d...@googlegroups.com
On Sun, May 4, 2014 at 6:45 PM, Brian Slesinsky <br...@slesinsky.org> wrote:

On Sun, May 4, 2014 at 3:49 PM, Max New <max...@gmail.com> wrote:
On Sat, May 3, 2014 at 8:30 PM, Brian Slesinsky <bsles...@gmail.com> wrote:

Perhaps I'm thinking too imperatively here, but this seems a bit weird to me since I'd have no notion of which way I'd want to nest the types, or even why I should modify a bunch of function signatures to call a new type of command. What does the extra structure give us?

Extra structure means you can make more distinctions. If there's only one command type I don't know if  a `Command a` is going to write to disk or display something to the user or make an HTTP request.

True, but why do you care? The only thing you can do with a command is return it to the runtime system so it will be executed.

What do you mean? You don't just hand them to the runtime system, you also write libraries and share code. The more specific type conveys more information about what the computation does to the users of a module. Maybe I want to let some library print to the screen but I don't want it to access the network. Or if one of your IO types lets you set the exit code and terminate the program you might want to know if that could to happen. If you don't want to make distinctions you can just use some larger type that combines the ones you need.
 
But this poses a problem when I want to change use my IO as an HTTP (IO a) and now I need to rewrite >>= as well (Haskell uses typeclasses to reduce the boilerplate).

If every command were already of type Command, you could already freely intermix commands. You wouldn't need to rewrite the >>= function (which isn't possible anyway because these are all effects handled by the runtime). It seems like rewriting >>= is an artificial problem introduced by trying to make a type distinction in the first place. Maybe it's needed in Haskell where there are lots of different monads, but it can be avoided in a new language.

Haskell actually only has one Command type that needs to actually hand off effects to the runtime system: IO. That doesn't eliminate the need for mixing monads because not all monads are runtime system effects. Common monads like Maybe and Either let you have failure/exception. There's also State for state-passing functions and Cont for continuation-passing functions. You want to combine them but still be able to use the monad interface, so programming with state and exceptions is just as easy as programming with only one of them.

You can still effectively split up IO into subpieces by making up new command types that only can use certain things like HTTP or File or whatever. I think implementation-wise it would definitely be easiest to have one universal Command type like IO and make all of your HTTP/File convert to that to be run.
 

Similarly, in Elm, all Elements are the same type, regardless of what they look like. In an older language, you might have multiple widget systems and need to convert between them. (In some older languages there isn't even agreement on a string type.)

- Brian

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/j9Da2UIA5xo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Brian Slesinsky

unread,
May 4, 2014, 10:10:52 PM5/4/14
to elm-d...@googlegroups.com
On Sun, May 4, 2014 at 5:29 PM, Max New <max...@gmail.com> wrote:
What do you mean? You don't just hand them to the runtime system, you also write libraries and share code. The more specific type conveys more information about what the computation does to the users of a module. Maybe I want to let some library print to the screen but I don't want it to access the network. Or if one of your IO types lets you set the exit code and terminate the program you might want to know if that could to happen. If you don't want to make distinctions you can just use some larger type that combines the ones you need.

Okay, it sounds like you want to use the type system to prove security-related theorems. I can certainly understand that. For example, in GWT, we have a SafeHtml type to help ensure that all user data is properly escaped when rendered in HTML pages.

However, it's a tradeoff. Specialized types come at the expense of flexibility for the maintainers of a library. In Java terms, a method that doesn't use the filesystem today might need to use it tomorrow because you moved some data to a configuration file. Then if you didn't original declare that it throws IOException, the workarounds can be a bit awkward. The stricter the type system is, the more painful certain changes will be.

Another example of overly-specific types: dozens of methods in GWT's Element class return integers for positions measured in pixels. This must have seemed reasonable at the time, but breaks badly now that browsers have started to support subpixel indexing. JavaScript uses doubles everywhere so this wasn't an issue for browsers; too bad we didn't do the same.

So, I would be conservative about what I use the type system to prove, particularly around effects. I can certainly see distinguishing between functions that don't have side-effects and commands that do (which is the whole point of a functional language), but if further distinctions need to be made, it seems like they should be with practical reasons in mind.

There are other ways to gain trust in libraries. An ordinary type system isn't going prove that a cosine function returns the correct answer; we depend on code review and testing for that. Types are kind of a "big gun" that you use when it's worth the cost. 

So, in a browser, we're already running in a sandbox. What mistakes are common enough when working with effects that we should guard against them using types?

- Brian

Max New

unread,
May 4, 2014, 11:59:37 PM5/4/14
to elm-d...@googlegroups.com
I completely agree that this is a tradeoff, and I think the decision can/should be up to the programmer. There should probably be one big command type that reexports all of the HTTP/File-specific commands but as Command a instead of HTTP a and/or an injection `lift : HTTP a -> Command a`. That way you only pay for the safety you use.

Max Goldstein

unread,
May 5, 2014, 12:32:32 PM5/5/14
to elm-d...@googlegroups.com
Despite my initial skepticism about an HTTP that doesn't send any network traffic, a single Command type is growing on me.

For IO actions (HTTP, File, Database), I like having a polymorphic Command that will result in an a when it successfully completes. Bound functions are about handling errors, either performing another IO or just returning (ahem) a default a. By abstracting away the fact that this is an HTTP or File command, it is no logner troubling to have a Command that is a trivial wrapping. Each library can have their own way to introduce Commands, but then common return and bind functions, and then to escape the Command, issue : a -> Signal (Command a) -> Signal a.

A unified Command has no trouble with trying to read a file from disk, and if it's not there, trying to get it from the network, and if it can't reach the network, return a string literal. Composability just happens. You can then present an interface of retrieving a file with a highly decoupled implementation (the interface doesn't know or care where the file came from).

The only problem is that there are still two types: the a you want and the e you pass to the error handler. So, with much ugliness, if you use bind for error handling then bind : Command a e -> (e -> Command a e') -> Command a e'. I'm not sure what to do about this.

What I found really helpful about Brian's Scene idea is that it's an asynchronously ending (trying not to say returning) function, but the value isn't an error. Although perhaps if we embrace two-type Commands, Scene a = Command Element a.

The two-type bind basically handles exceptions internally, and keeps trying things until it has a value of the success type. (Or with signals, if you never get a success type because a return was never bound, then just don't propagate an event. Skip becomes implicit.) I like the two-type system far too much considering that Haskell only needs one type. Can someone please tell me / point me to a tutorial about what happens when Haskell's IO monad encounters an error?

Brian Slesinsky

unread,
May 5, 2014, 1:01:58 PM5/5/14
to elm-d...@googlegroups.com


On Monday, May 5, 2014 9:32:32 AM UTC-7, Max Goldstein wrote:

What I found really helpful about Brian's Scene idea is that it's an asynchronously ending (trying not to say returning) function, but the value isn't an error. Although perhaps if we embrace two-type Commands, Scene a = Command Element a.

This isn't quite right. A Scene would display a (Signal Element) while running and return a different value when complete. (Think about how a dialog box works.)

The difference between a Command and Scene is that a Command has no user interface and runs in the background while the main UI is still running. A Scene would replace the regular UI while it runs, or possibly run in a subview of the main UI if we can figure out how to make that work. The awkwardness in Elm is that every possible dialog would need to declare a constant of type Signal Element, and somewhere else we need to make sure we display the correct dialog. This is because Elm fundamentally doesn't support lifecycles for signals.

The entire Elm program does have a lifecycle, though. If scenes are full-screen then we could consider them Elm subprograms that encapsulate all their signals (which are scoped to the scene and inaccessible outside it). Then the signals would have the same lifecycle as the scene.

Anyway, Commands seem like a much easier problem.

- Brian

Evan Czaplicki

unread,
May 5, 2014, 1:15:04 PM5/5/14
to elm-d...@googlegroups.com
Yeah, I was really skeptical too, but I see how it's really handy now. Brian, thanks for being persistent in arguing through our Haskell biases :) If someone wants to use a subset of the most general command, it should be pretty easy.

data Http a = Http (Command a)

And then reexport the acceptable functions. Seems doable.

I have some concerns that are not fully addressed yet though:

Naming: I like the word "command" as a general way to refer to monadic APIs. It means we never need to talk about monads, only "command syntax" and return/andThen. So State, Error, Http, and IO are all commands. If the general version is explicitly called a Command, it seems to undermine this. "What is (State Int String)?" It's a command. A Command? No it's like a Command, but this command is less general.

It seems suboptimal. I was thinking maybe just having Effectful as a catchall. So you'd have (Effectful Int) and know it's going to do some stuff. I'm going to use that notation for the rest of this post, but I think we should brainstorm it more.

Errors and State: I use these quite a lot, often together. @mgold mentioned that our rough idea does not have a place for errors, so it seems like we might want the most general thing to be (Effectful value err), so we get that it can error in there too. But we still have no place to thread state through... so maybe we really want (Effectful value state err) but things are looking pretty complex at this point.

Alternative Direction: It feel like the Idris way gives everyone what they want. You have a fully general Command type that can hold a selection of commands. As shown in the PureScript post and Idris paper, this means you can introduce the possibility of error, and then when it is caught, that comes out of the types.

In this world, calling the general thing a Command is cleaner because it really is an explicit collection of commands. You can also make a type alias for the Effectful command to tuck a bunch of those details away. I have a vague sense that extensible records and some trickery may be enough to get to this place, but if you need dependent types, it's off the table for Elm :/

I need to do more research and asking around to figure out if this is a real option though.


--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Max Goldstein

unread,
May 5, 2014, 1:16:30 PM5/5/14
to elm-d...@googlegroups.com
This isn't quite right. A Scene would display a (Signal Element) while running and return a different value when complete. (Think about how a dialog box works.)
Once you have your scene, you'd run it with (adding the auxiliary type) issue : a -> Signal (Command a e) -> Signal a. I'm thinking main = issue empty (constant scene). I haven't considered how a non-constant signal of scenes would work. Your scene must pass the result of the dialog box, represented as the auxiliary type, into the next scene (with bind).

 or possibly run in a subview of the main UI if we can figure out how to make that work.
That would be very cool but it breaks the whole output->input chaining. It will require more thought.
 
The entire Elm program does have a lifecycle, though. If scenes are full-screen then we could consider them Elm subprograms that encapsulate all their signals (which are scoped to the scene and inaccessible outside it). Then the signals would have the same lifecycle as the scene.
We're on the same page here. A command must incapsulate the knowledge of how to issue it (I'd imagine the general issue being implemented with dispatch: command, go issue yourself!). This knowledge can be of HTTP requests of or scoping signals, starting and pausing the graph. Instead of lifecycles for signals, we switch among multiple static signal graphs.

Max Goldstein

unread,
May 5, 2014, 1:43:22 PM5/5/14
to elm-d...@googlegroups.com
"What is (State Int String)?" It's a command. A Command? No it's like a Command, but this command is less general.
This is only a problem if someone creates State by wrapping Command in an ADT. A (Command Int String) doesn't seem problematic.
 
I was thinking maybe just having Effectful as a catchall. So you'd have (Effectful Int) and know it's going to do some stuff.
Resist the urge. All commands should be Commands. See the third paragraph of my first post today for the kind of composability this gives you. And return would create an Effectful that (possibly) has no effect (those that do would be introduced in a way specific to that effect, e.g. with a file path or HTTP request).
 
So maybe we really want (Effectful value state err) but things are looking pretty complex at this point.
I can't say for certain but I'm hopeful that you only need two types. A main, and an auxiliary. It's a bit like an implicit Either. The main is always the value you care about and the auxiliary does not escape the command (the Command). Things should be implemented such that auxiliary can serve as state. If your stateful operation can fail, add the error to the state, or maybe nested commands could work.

Max New

unread,
May 5, 2014, 2:13:26 PM5/5/14
to elm-d...@googlegroups.com

IO has a weird exception mechanism that I try to avoid whenever possible, preferring `EitherT e IO a` for some `e`. The Exception stuff is documented with references at the top here: http://www.haskell.org/ghc/docs/7.6.2/html/libraries/base/Control-Exception.html. Note that this uses type system stuff that's not in elm (existential types?).

Max Goldstein

unread,
May 5, 2014, 2:17:47 PM5/5/14
to elm-d...@googlegroups.com
If it's something you try to avoid, it's not worth copying, which is really all we care about. This also means I'm not as crazy as I thought to propose adding a second type variable to monads/commands. Thanks for the info.

Brian Slesinsky

unread,
May 5, 2014, 2:18:11 PM5/5/14
to elm-d...@googlegroups.com
Error-reporting is more general than commands. A pure function may still be partial and report errors when it fails. So it seems like we would still want some kind of generic Result type which works both for failed commands and for failed functions.

However, like with Commands, it's good to make them composable. If you have a command that starts out doing an HTTP request and then is changed to read from a file, the possible errors will likely change too, but maintenance would be easier if the error type didn't change. This suggests that result should be (Result a) and not (Result e a) like Either. Something like this:

data Error = Err String | ErrWithCause String Error
data Result a = Failed Error  | Ok a

Then we could have a (Command a) that returns a (Result a) without making the error type configurable. And yet it still supports reporting an error (of fixed type).

I'm not sure if we could get away with such a simple and non-configurable design, but it would make things a lot easier to explain if we could. It's nice that Element is a simple type.

- Brian



--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/j9Da2UIA5xo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Evan Czaplicki

unread,
May 5, 2014, 2:24:17 PM5/5/14
to elm-d...@googlegroups.com
Has everyone read this and this? Perhaps we all have, but I want to make sure we're all on the same page in terms of understanding the related work out there.


--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Max Goldstein

unread,
May 5, 2014, 2:50:44 PM5/5/14
to elm-d...@googlegroups.com
Brian, I admire that you're trying to make things more standard, but I don't think we can "get away with" that design. HTTP can error with a status code and a message, and maybe the headers are valuable if you're in the middle of a protocol, or maybe it was blocked by the same origin policy, or maybe it timed out. Each effect is going to want a rich and customized error type to give lots of choices to the writer of bound functions. And this would completely kill your Scene idea as a command, which impressed upon me that the auxiliary type is not necessarily an error. It's more accurately the input to a continuation. You are correct that in theory, total functions are a strict subset of pure functions. But in code, commands do more than handle errors.

Brian Slesinsky

unread,
May 6, 2014, 12:14:53 AM5/6/14
to elm-d...@googlegroups.com


On Monday, May 5, 2014 10:16:30 AM UTC-7, Max Goldstein wrote:
This isn't quite right. A Scene would display a (Signal Element) while running and return a different value when complete. (Think about how a dialog box works.)
Once you have your scene, you'd run it with (adding the auxiliary type) issue : a -> Signal (Command a e) -> Signal a. I'm thinking main = issue empty (constant scene). I haven't considered how a non-constant signal of scenes would work. Your scene must pass the result of the dialog box, represented as the auxiliary type, into the next scene (with bind).

Does that mean that when a Scene is running, it takes over the screen somehow, but it can be mixed (using bind) with ordinary non-UI commands? I hadn't thought of that; I was thinking of them as distinct types.

It seems a bit problematic; presumably you can run multiple non-UI commands at once on separate threads, but only one Scene should run at once if it takes over the screen.

I'm also not sure what you mean by an "auxiliary type". Is that different from the usual output type (return type)?

- Brian

Brian Slesinsky

unread,
May 6, 2014, 12:24:41 AM5/6/14
to elm-d...@googlegroups.com

On Monday, May 5, 2014 11:50:44 AM UTC-7, Max Goldstein wrote:
Brian, I admire that you're trying to make things more standard, but I don't think we can "get away with" that design. HTTP can error with a status code and a message, and maybe the headers are valuable if you're in the middle of a protocol, or maybe it was blocked by the same origin policy, or maybe it timed out. Each effect is going to want a rich and customized error type to give lots of choices to the writer of bound functions.

Yes, ideally. However, I'm a bit worried that we are running into a heterogenous list problem. For example:

data Error = Err String | ErrWithCause String Error

This is basically a list. If we replace each String with a different error type, how do we determine the type of the overall chain? How do we iterate over it to print out something like a stack trace?

I've read that in functional programming, if you think you need a heterogenous list then you're probably doing something wrong, but I don't see how to rewrite this.

- Brian

Message has been deleted

Max Goldstein

unread,
May 6, 2014, 2:05:17 AM5/6/14
to elm-d...@googlegroups.com
I'm not sure what to do here. On one hand, you seem to not understand what I've been trying to say, which is my fault, my failure to communicate. On the other, I suspect that I'm going on too long about this, and should be doing more reading than posting. I've tried to answer your questions without writing a whole proposal. There's a lot to criticize but let me try to get it out first. But since Haskell's IO exceptions are apparently pretty bad and the monad method fail is disliked, I think it's worthwhile to see if there's some other way to handle failure.

The auxiliary type is what I originally called the error type. It's the second type variable of the Command, which I added. ("Main" and "auxiliary" would suggest type variables m a but that's just asking for trouble from Haskell folks. So a b then.) A command is a computation that may produce either a main type or an auxiliary type, and the goal of the bound functions is to produce a main type when it produces the auxiliary type.

issue : a -> Signal (Command a b) -> Signal a
bind : Command a b -> (b -> Command a b') -> Command b'
return : a -> Command a b

Semantics of issue (issue a command, get it?): when the command has an a, pass it on; when it has a b, try the bound functions; when out of bound functions, propagate no event. Commands may have effects, but they can only be run as part of signals (in issue), preserving purity. The presence of signals is another reason Haskell won't have The Answer for Elm. The auxiliary type does not escape the command. Thus return can keep it unbound, since it always produces the main type. The auxiliary type and bound functions are like localized exceptions or continuations.

So to answer your question, you can bind any Command to happen after your Scene is over, so long as its main type is Element. Yes, this would take over the program, and the resulting signal of Elements would be main. If you want multiple Scenes at once, it throws a monkey wrench in the entire system. If we pursue this further, we will no doubt want other combinators, so you can send an HTTP request with data from a completed Scene while also rendering a waiting indicator.

(The best idea I have, which I don't really like, allows you to send a Command and handle either output: f : (a' -> Command a) -> (b' -> Command a) -> Command a' b' -> Command a b. So you can draw an error message and a success message and spin off an HTTP request and call it a command that draws Elements. No idea how you'd thread an fps signal if you want animation, or if your command is made by lifting bind on a signal of Elements, like main in a module. Huh.)

I'm not sure what to make of the heterogenous list problem. If your file read was not successful, then you'll get back an auxiliary type with error information rather than the main type of String. When your Scene terminates, it puts all the information it thinks is valuable into a term of the auxiliary type; previously it had been spewing Elements as its main type. Because the auxiliary type can change with each bind, we never really get a list. It's CPS, so you're not really saving a stack. You just need to know what the command that failed and its input. But I feel I am missing the thrust of your concern.

I've managed to poke enough holes in writing this that I'm convinced the problem is just Really Hard, and I'm interested in whether the community thinks I'm wasting everyone's time (sorry!) or if I could be on to something with two types.

Raoul Duke

unread,
May 6, 2014, 3:44:34 PM5/6/14
to elm-discuss
> dependency is a breaking API change, and this bubbles up. If A wants to
> start using the filesystem then B (which calls A) also needs it, and C
> (which calls B) also needs it, so you get type errors from code changes that
> are far away.

devil's advocate for a second: maybe that's just life and not such a
bad thing from the vantage point of having our code be clear and
explicit. if somebody wants to avoid that they can wrapper all their
own errors at their level into some void* and return that all the
time. but that's their decision to sweep useful information under the
carpet.

Brian Slesinsky

unread,
May 7, 2014, 12:57:00 AM5/7/14
to elm-d...@googlegroups.com
On Mon, May 5, 2014 at 11:05 PM, Max Goldstein <maxgol...@gmail.com> wrote:

The auxiliary type is what I originally called the error type. It's the second type variable of the Command, which I added.

Okay, I just wanted to confirm that's what you meant.
 
So to answer your question, you can bind any Command to happen after your Scene is over, so long as its main type is Element. Yes, this would take over the program, and the resulting signal of Elements would be main. If you want multiple Scenes at once, it throws a monkey wrench in the entire system. If we pursue this further, we will no doubt want other combinators, so you can send an HTTP request with data from a completed Scene while also rendering a waiting indicator.

An Element is just one frame of an animation so I don't think that would be enough; chaining Commands together to do animation would be rather awkward compared to using a (Signal Element).

Also, I think anywhere a command returns a value, you probably want the ability to return an error, and that applies to Scenes as well. So a Scene has three values associated with it, the user interface, its return value, and possibly an error that gets returned instead.

It seems better for a Scene and a Command to be different types, and to have a way to construct a scene from a Command if that's what you want to do. Maybe there could be a function to display a non-animated screen while the command is running; something like this:

runAndDisplay : Command a ->  Element -> Scene a

Brian Slesinsky

unread,
May 7, 2014, 1:19:49 AM5/7/14
to elm-d...@googlegroups.com
On Tue, May 6, 2014 at 12:44 PM, Raoul Duke <rao...@gmail.com> wrote:

devil's advocate for a second: maybe that's just life and not such a
bad thing from the vantage point of having our code be clear and
explicit. if somebody wants to avoid that they can wrapper all their
own errors at their level into some void* and return that all the
time. but that's their decision to sweep useful information under the
carpet.

Sure, that's commonly done in imperative languages but I'm not sure it applies here. If you can sweep a dependency under the carpet so that a command can access the network even though it doesn't claim to, there's no point in adding dependency checking to the type system.

Raoul Duke

unread,
May 7, 2014, 1:17:51 PM5/7/14
to elm-discuss
> Sure, that's commonly done in imperative languages but I'm not sure it
> applies here. If you can sweep a dependency under the carpet so that a
> command can access the network even though it doesn't claim to, there's no
> point in adding dependency checking to the type system.

which to me somewhat argues for the "breaking changes" being a good
thing. if A depends on B depends on C and C changes *then A has a
dependency which has changed*. to ignore it is to defeat the purpose
of the dependencies.

i must assume i'm not parsing the thread well enough to understand the
middle grounds people must have proposed so far? :-)

Brian Slesinsky

unread,
May 8, 2014, 12:43:56 AM5/8/14
to elm-d...@googlegroups.com


On Wednesday, May 7, 2014 10:17:51 AM UTC-7, Raoul Duke wrote:

which to me somewhat argues for the "breaking changes" being a good
thing. if A depends on B depends on C and C changes *then A has a
dependency which has changed*. to ignore it is to defeat the purpose
of the dependencies.

i must assume i'm not parsing the thread well enough to understand the
middle grounds people must have proposed so far? :-)

One part of the debate is how granular to make the permissions. At the one extreme, we could have no type checking and any command is allowed to perform any effect. That's easiest to implement, but some have argued that Elm should check permissions. At the other extreme, Elm could have a lot of very fine-grained permissions to add to the whitelist. For example, instead of granting "can connect to the Internet", you could give a command permission to connect to websites A, B, and C. I don't think anyone has proposed that for Elm, but it's something I've wished Android apps could do.

There's also a question of whether it should be a whitelist, a blacklist, or some of both, and what the default should be if you don't declare anything.

Here's a radical idea: effects type-checking is sort of like using access control lists for authorization, because creating a command is different from being allowed to return it. Instead, suppose we had something like capabilities? Then just having access to the function for creating an command would automatically grant permission to use it. Under this scheme, the function for creating a command shouldn't be a constant in a module somewhere. Instead, main would declare that it takes a record parameter with a field for each constructor needed anywhere in the program. It would then would pass the record along to every function that needs to create something (they would declare the subset of fields they need).

This is quite similar to dependency injection, which is all the rage in Java, though it's a bit controversial as some dependency injection libraries can be rather complicated. I don't think it fits very well with Elm since signals are usually constants, but thought I'd bring it up as something interesting to think about.

- Brian

John Nilsson

unread,
May 8, 2014, 9:15:02 AM5/8/14
to elm-d...@googlegroups.com
Newspeak has en interesting approach to this, might want to check it out: http://lambda-the-ultimate.org/node/3544

BTW DI isn't only for Java. AngularJS depends heavily on DI for its modularization approach: https://docs.angularjs.org/guide/di

BR,
John

John Nilsson

unread,
May 8, 2014, 9:16:18 AM5/8/14
to elm-d...@googlegroups.com
Sorry, this is a better text on DI in AngularJS https://docs.angularjs.org/guide/unit-testing
Reply all
Reply to author
Forward
0 new messages