Banishing all runtime errors

564 views
Skip to first unread message

Evan Czaplicki

unread,
Feb 3, 2015, 9:02:30 AM2/3/15
to elm-d...@googlegroups.com
I want one of the selling points of Elm to be "never have runtime errors again". Debugging runtime errors sucks, and people from Java, Python, JavaScript, C#, etc. can all identify with the pain. We have also had good success saying "What if there was never a runtime error?"

Right now, we're practically there, but you can cause a runtime error if you really want in two ways:
  • Partial functions (stuff like head and maximum)
  • Incomplete pattern matches (\(Just x) -> ...)
Laszlo made some changes to core to remove all partial functions. This will be a breaking change, and it'll be out with the next release for sure.

Longer term, we'd like to throw errors when incomplete pattern matches are detected (like OCaml) and suggest that people explicitly have a pattern like this if they need something partial :

case ... of
  Just x -> ...
  _ -> Debug.crash "This will never happen. I'm super clever, warnings are for other people."

In this world, the only time you will have runtime errors is when you do it by hand. You have to literally write down "crash this program, burn the whole thing" which I think is kinda nice. It's clear who owns this mistake :)

Richard may have more to add on this topic?

Laszlo Pandy

unread,
Feb 3, 2015, 9:28:37 AM2/3/15
to elm-d...@googlegroups.com
In the other thread Do Bi identified that Regex.regex will crash if given a bad input.

This is the same issue as "mergeMany []" which will crash immediately. Both functions were designed to only be used with literals (ie. fixed string or list) and so crashing on startup isn't bad like a runtime error.

But Do Bi showed that if someone can, they will use these functions with dynamic input (signal functions are safe but anything else needs to be done with care).

--
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.
For more options, visit https://groups.google.com/d/optout.

Hassan Hayat

unread,
Feb 3, 2015, 9:37:10 AM2/3/15
to elm-d...@googlegroups.com
+ 1BILLION

I totally agree, plus it pushes that idea of having the main core 90% of the app being pure and immutable and safe and all the effects are on the edges. Since real apps probably will have to do some sort of JS interop, it'll drastically reduce the places where to look for runtime errors. If you see an "undefined is not a function", it's in the Javascript, not in the Elm. Plus, it'll make things super testable, and super reliable. (like, you don't have to go through the craziness of asking, "wait a second... should I check for null even if this function only deals with numbers? What if someone passes a string and that gets coerced? arrrghh".

Joey Eremondi

unread,
Feb 3, 2015, 10:01:33 AM2/3/15
to elm-d...@googlegroups.com
I might be able to implement a totality checker as a project for my Automatic Program Analysis class. I'd have to double check with my professor that it would be allowed, but I'd love to get credit for contributing to Elm.

--

Evan Czaplicki

unread,
Feb 3, 2015, 10:03:31 AM2/3/15
to elm-d...@googlegroups.com
Joey, that would be really cool and massively appreciated, please let us know what your prof says!

Janis Voigtländer

unread,
Feb 3, 2015, 10:15:08 AM2/3/15
to elm-d...@googlegroups.com

I think this is a very good development.

In addition, I think it means that the Signal analogue of List.filterMap urgently needs to be added to elm-lang/core (with a native implementation). Because a) it provides important functionality which b) will be impossible to write purely in Elm as soon as you outlaw incomplete pattern matches. (Unlike List.filterMap, which can be written purely in Elm without incomplete pattern matches. Everybody is welcome to prove me wrong about Signal.filterMap, of course.) And even if you don’t outlaw incomplete pattern matches outright with the next release, it is best to nudge people into using a provided Signal.filterMap as early as possible instead of everybody developing a habit of writing equivalent functionality on their own and at the price of using some variant of (\(Just x) -> x).


--

Janis Voigtländer

unread,
Feb 3, 2015, 10:21:39 AM2/3/15
to elm-d...@googlegroups.com
I hope Jurriaan will say yes, but given the constraints expressed on the course page about what an eligible project will be, I fear he won't. I think the "you must use a type and effect system approach" doesn't fit very well with a totality checker.

Joey Eremondi

unread,
Feb 3, 2015, 10:36:00 AM2/3/15
to elm-d...@googlegroups.com
I'm not sure, but it sounded pretty similar to the "Control-flow analysis with data-structures" that was listed as the default assignment on the page.

We haven't covered type-and-effect systems yet, so I'll admit I don't fully understand what I'm getting myself into, but from the notes I've read it sounds like they have applications outside modeling effects.

My apologies if this is a wild goose chase.

Janis Voigtländer

unread,
Feb 3, 2015, 10:43:37 AM2/3/15
to elm-d...@googlegroups.com
Sorry, I definitely didn't want to discourage you from trying or asking.

Maybe it can be made to fit the requirements. But I assumed that by totality checker something like Neil Mitchell's Catch was meant, which is largely local syntactic checks (to be done carefully and all, to catch all dangerous cases etc.) and/but wouldn't really make use of the more semantic approach of a types-and-effects system. But anyway, good if you can try. And have fun with the course!
 

Paul Chiusano

unread,
Feb 3, 2015, 10:57:29 AM2/3/15
to elm-d...@googlegroups.com
I "totally" agree this would be a very good thing.

Paul :)

Max Goldstein

unread,
Feb 3, 2015, 6:12:38 PM2/3/15
to elm-d...@googlegroups.com
Yes, my only concern is that we will need to be open to more sophisticated functions on Maybes. Rather than asking for language inclusion, this might be another reason to put together a communal 3rd party package of neat extensions to add.

Janis Voigtländer

unread,
Feb 4, 2015, 1:56:06 AM2/4/15
to elm-d...@googlegroups.com

There is already such a library that could be home for Signal.filterMap, namely Apanatshka/elm-signal-extra.

But:

That library, AFAIK, so far only contains functions that can be written purely in Elm, but aren’t part of the standard libs. So it’s stuff that a user could already have in their code without resorting to Native. Without incomplete pattern matches, Jeff wouldn’t be able to include Signal.filterMap in Apanatshka/elm-signal-extra unless he makes that a library that uses JS/Native, changing its character. So I still maintain that before incomplete pattern matches are, morally or technically, outlawed, Signal.filterMap should be added to elm-lang/core. :)


2015-02-04 0:12 GMT+01:00 Max Goldstein <maxgol...@gmail.com>:
Yes, my only concern is that we will need to be open to more sophisticated functions on Maybes. Rather than asking for language inclusion, this might be another reason to put together a communal 3rd party package of neat extensions to add.

--

Richard Feldman

unread,
Feb 4, 2015, 2:14:40 AM2/4/15
to elm-d...@googlegroups.com
Love it! Not having to worry about accidentally making a pattern match incomplete during refactors will make them even more pleasant than they already are. :D

Alex Neslusan

unread,
Feb 4, 2015, 7:36:58 AM2/4/15
to elm-d...@googlegroups.com
I'd be supportive of this. Another thing to watch for would be making sure nobody's throwing exceptions in any Native modules...

Jeff Smits

unread,
Feb 4, 2015, 7:45:51 AM2/4/15
to elm-discuss
I like the general idea. Definitely better than Haskell with all the non-total functions in Prelude.

I wouldn't mind implementing filterMap in my library using Debug.crash.

--

Janis Voigtländer

unread,
Feb 4, 2015, 8:05:36 AM2/4/15
to elm-d...@googlegroups.com

Okay, so should I make a PR (adding Signal.filterMap) against your lib or the standard lib? :)

Joey Eremondi

unread,
Feb 4, 2015, 8:11:10 AM2/4/15
to elm-d...@googlegroups.com

Could we have different levels of safety? There's unsafe, where a pattern match can fail, annotated, where Debug.error can be called, and safe, where all functions are total.

You'd want annotated for development, but safe for production, since you should probably be catching all errors and showing messages instead.

Janis Voigtländer

unread,
Feb 4, 2015, 8:15:17 AM2/4/15
to elm-d...@googlegroups.com

For the specific example, that would bring us back to where Signal.filterMap cannot be used in production code, unless it becomes part of a blessed native library (e.g., elm-lang/core). The point here is that the Debug.crash that would appear in the implementation of Signal.filterMap would be 100% certain to never be executed, just the compiler cannot be convinced of that.

Joey Eremondi

unread,
Feb 4, 2015, 8:33:09 AM2/4/15
to elm-d...@googlegroups.com

Right. Maybe we need a special error, one that says "this is impossible but we're not dependently typed so we can't express that" and one that says "to do implement this case later"

What's the type of Signal.filterMap again?

Janis Voigtländer

unread,
Feb 4, 2015, 8:35:57 AM2/4/15
to elm-d...@googlegroups.com

Signal.filterMap : (a -> Maybe b) -> b -> Signal a -> Signal b

Max Goldstein

unread,
Feb 4, 2015, 8:49:58 AM2/4/15
to elm-d...@googlegroups.com
Oh nice, it's a more general form of (Signal (Maybe a) -> a -> Signal a). Are the semantics to keep the old b (not emit and event at all) when the function produces a Nothing, or to use the default? I think the former is more useful.

Janis Voigtländer

unread,
Feb 4, 2015, 8:53:21 AM2/4/15
to elm-d...@googlegroups.com

Another current source of runtime errors that would be nice to instead catch at compile time: http://share-elm.com/sprout/54d223ade4b05f9c59b2587b (using == at a function type). IMHO, that program should be rejected by the compiler. What is a use case where calling == at a function type seems justified?


--

Janis Voigtländer

unread,
Feb 4, 2015, 8:55:26 AM2/4/15
to elm-d...@googlegroups.com

Definitely the former (not emitting an event if the function produces a Nothing). That’s like in List.filterMap, where no output list element is emitted if the function produces a Nothing.


2015-02-04 14:49 GMT+01:00 Max Goldstein <maxgol...@gmail.com>:
Oh nice, it's a more general form of (Signal (Maybe a) -> a -> Signal a). Are the semantics to keep the old b (not emit and event at all) when the function produces a Nothing, or to use the default? I think the former is more useful.

--

Hassan Hayat

unread,
Feb 4, 2015, 8:59:37 AM2/4/15
to elm-d...@googlegroups.com
Well, some people have found certain cases where they can actually define equality on functions:

http://math.andrej.com/2007/09/28/seemingly-impossible-functional-programs/

equal :: Eq y => (Cantor -> y) -> (Cantor -> y) -> Bool

Hassan Hayat

unread,
Feb 4, 2015, 9:01:36 AM2/4/15
to elm-d...@googlegroups.com
Ok, I'm being facetious. I agree, equality between functions should be rejected. It makes no sense. 

Janis Voigtländer

unread,
Feb 4, 2015, 9:13:47 AM2/4/15
to elm-d...@googlegroups.com

I any case, I don’t have a problem with Escardo’s equality on some specific function types, just as I don’t have a problem with equality between functions of type Bool -> Bool (because it can be implemented by exhaustive enumeration, but that should be the programmer’s job if desired). I’m having a problem with equality on arbitrary function types.

Hassan Hayat

unread,
Feb 4, 2015, 9:19:30 AM2/4/15
to elm-d...@googlegroups.com
Yeah, that shouldn't be a thing. We should have an error that says "you cannot test functions for equality".

Hassan Hayat

unread,
Feb 4, 2015, 9:32:54 AM2/4/15
to elm-d...@googlegroups.com
That now leaves us with the big runtime error... "maximum call stack size exceeded". For this thing, we can:

1) wait for JS to get tail call optimization
http://kangax.github.io/compat-table/es6/

2) continue warning about it and pushing people to use things like the Trampoline library (or, my Native library, elm-loop).

3) find a way to get tail call optimization ourselves (at least just something like one function calling just itself).

Joey Eremondi

unread,
Feb 4, 2015, 11:48:25 AM2/4/15
to elm-d...@googlegroups.com
Maybe I'm misunderstanding, but wouldn't this do Signal.filterMap without any partial matches?

http://share-elm.com/sprout/54d24d27e4b05f9c59b258ba

--

Janis Voigtländer

unread,
Feb 4, 2015, 12:03:10 PM2/4/15
to elm-d...@googlegroups.com
This doesn't have the desired semantics. It repeats the old value on the signal when the mapped function gives Nothing for the input value. The desired semantics is that in that case the output signal does not fire at all.

Hassan Hayat

unread,
Feb 4, 2015, 12:44:12 PM2/4/15
to elm-d...@googlegroups.com
By the way, I would like to further motivate this goal. 

Do you guys use or have used Atom to write your code? Well, if you have, then you've probably encountered little red boxes that appear once in a while on the top right to signify that something has crashed badly. 

If you haven't used Atom, you might be thinking, "hey, that happens with almost any piece of software once in a while, it's probably not a big deal, they'll fix it quickly". Wanna see how many such bugs have been reported:

https://github.com/atom/atom/issues?q=Uncaught

21 FREAKING PAGES OF BUG REPORTS!!!!!!!! 

These are not bugs where something does something unintended... 

No no no... All these bugs are runtime crashes! Crashes due to typeerrors, missing null checks, weird behavior between packages/plugins, or just things where people go "I have no idea what's going on". Pure runtime crashes. Uncaught exceptions. Stuff inside try catch blocks. Seriously.

Worst of all, these are only the ones in Atom's issue tracker. Then there are the bugs in the issue trackers of the specific packages' issue trackers. This stuff is beyond nasty. Seeing those 21 pages of bugs made me thing, "Dammit, this stuff must end!"

Imagine if the good folks over at Github could use something like Elm, we'd save them loads of headaches. Seriously. Like, we can make this stuff go away. And all that will be left will be bugs that are due to incorrect code or algorithms (incorrect in the sense that it gives the wrong answer). And this problem was kinda solved by the industry: TDD. Write/generate failing tests then write code to pass those tests, rinse and repeat and you're good. Plus, having 0 mutations means that these tests mean something. Like, they're actually honest-to-goodness reliable. 

Max Goldstein

unread,
Feb 4, 2015, 10:07:02 PM2/4/15
to elm-d...@googlegroups.com
I support detecting equality of function types as long as it doesn't make me use equatable type signatures everywhere. If there's no burden on the programmer, by all means. it should be possible to distinguish a function from non-function, and special-case == and /=.

This is assuming all terms of kind * (i.e. everything that isn't a function) is equatable. Are signals equatable? (I'd expect that to be object identity, not semantically.) Dicts and sets fail equality silently. And identity == identity evaluates to true because of object identity; I assume it's safe to break this behavior.

Joey Eremondi

unread,
Feb 6, 2015, 5:54:10 PM2/6/15
to elm-d...@googlegroups.com
Here's take 2 at implementing it with only total functions:

http://share-elm.com/sprout/54d545d4e4b05f9c59b259ee

Janis Voigtländer

unread,
Feb 7, 2015, 1:04:57 AM2/7/15
to elm-d...@googlegroups.com

Yes, that does it!

But I hope you feel somewhat guilty about the Nothing -> (False, snd oldb) line. The point being that the snd oldb value there (and actually computing it) does not make much sense since you’ll never access the second component of a pair whose first component is False. But you have to put something there for type reasons, so add that dummy value. To make that aspect even more apparent, consider this version: http://share-elm.com/sprout/54d5a70be4b05f9c59b25a15. It improves upon your version in that it does not need a foldp (instead just a map in that place) and does avoid computing the snd oldb projection whose value would be ignored anyway. That version even more blatantly abuses a dummy value of type b to put into the second component of (False, _) pairs “just because one has to”. I wouldn’t want a programmer to have to write your or my version, because in both versions the Nothing ~= (False, dummy_value) “modelling” just seems wrong.

BTW, in a world like discussed by Rehno in a recent thread, where (certain) signals need not have an initial value, and thus the type of filterMap (for that kind of signals) would be just (a -> Maybe b) -> Signal a -> Signal b, one couldn’t play any of the above tricks (because no b value would be available up front to abuse), even though filterMap would still make perfect sense and be useful.

Justin Manley

unread,
Mar 4, 2015, 10:40:58 PM3/4/15
to elm-d...@googlegroups.com
+1

This is a great idea!

I just spent ~6 hours refactoring some code to remove all calls to List.head, which was innocently peppered throughout my code, so I'm feeling the pain of runtime errors in Elm.

Same goes for inexhaustive case statements.

Hassan Hayat

unread,
Mar 4, 2015, 10:56:00 PM3/4/15
to elm-d...@googlegroups.com
Btw, in the next version of Elm, List.head will return a Maybe. It's already in the master.

https://github.com/elm-lang/core/blob/master/src/List.elm#L72

Hopefully that gets out soon. I recommend implementing your own "head" function for the moment. I call it "first":

first : List a -> Maybe a
first
list = case list of
 
[] -> Nothing
  x
:: xs -> Just x
 

Richard Feldman

unread,
May 27, 2015, 1:24:38 AM5/27/15
to elm-d...@googlegroups.com
I ran git grep crash on elm-lang/core to see what intentional runtime exceptions remain in the core library. Here's the list: https://gist.github.com/rtfeldman/2436caed0b9411272aac

It looks like these break down into three categories:

1. Dict pattern matches that should never come up (so no one should ever see these unless there's a bug)
2. Json failing to decode (meaning the fix is external to your Elm code, which is already an acceptable runtime exception case)
3. Signal.mergeMany given an empty list

The last one surprises me. Is that case really exception-worthy? Personally I'd expect it to resolve to some sort of trivial Signal.

Janis Voigtländer

unread,
May 27, 2015, 2:40:44 AM5/27/15
to elm-d...@googlegroups.com

What should Signal.mergeMany [] : Signal a return? It would at least need an initial value, but the type a is unknown at this point. So there is no trivial signal to put there. In a world with streams that need no initial value, things would be different.

About the general exercise: This covers only intentional runtime exceptions that occur from within the Elm part of core, but core also has a JS part. And in there live things like: https://github.com/elm-lang/core/blob/2.0.1/src/Native/Utils.js#L53-L54. Maybe there are other interesting cases?

--

Janis Voigtländer

unread,
May 27, 2015, 3:23:15 AM5/27/15
to elm-d...@googlegroups.com

BTW, two of the Dict cases with a call to crash are actually statically known to never come up. The min and max functions in which they occur are completely superfluous. See https://github.com/elm-lang/core/pull/254.

2015-05-27 7:24 GMT+02:00 Richard Feldman <richard....@gmail.com>:

--

Richard Feldman

unread,
May 27, 2015, 5:29:48 AM5/27/15
to elm-d...@googlegroups.com
Ah! Good point about the initial value.

Given that, what if changing it to mergeMany : Signal a -> List (Signal a) -> Signal a is a good idea, for the same reasons that changing head to head : List a -> Maybe a was a good idea?

You could think of it as "merging 0 or more additional signals into the given base signal," and if you wanted to take an arbitrary List and pass it to this revised mergeMany, you could just do:

case listOfSignals of
  first
:: rest -> mergeMany first rest
 
[] -> Debug.crash "Blowing up in user code is nicer than blowing up in library code!"

Here's how some of my Dreamwriter code would look if refactored based on this: https://gist.github.com/rtfeldman/88bbb6d89ffa6f8f52fd

Richard Feldman

unread,
May 27, 2015, 5:50:39 AM5/27/15
to elm-d...@googlegroups.com
For what it's worth, here's the same exercise performed on git grep throw:


Categories:

1. `Array.get` is given an index out of bounds. It suggests using `getMaybe` or `getWithDefault` instead. If `get` needs to exist for performance reasons, might it be better to rename it to something like `getDangerously` and renaming `getMaybe` to `get`?
2. Division by zero. Having this be a runtime exception is standard, but I don't actually think it's crazy to think about doing this as a `Maybe`, especially since it's so easy to define your own unsafe version using `Debug.crash` if that's what you want. (Or perhaps if there's a performance concern, a `divideDangerously` which you could then locally alias to the infix operator of your choice if desired?)
3. `Debug.crash` implementation - obviously needs to `throw`. ;)
4. JSON decoding - discussed previously on the Elm side; seems fine.
5. `Native/Runtime` - throwing exceptions if JS land is not in the expected state; as with JSON, seems fine.
6. `Native/Utils` - throwing exceptions if you try to compare things that cannot be compared. I believe all the sub-cases of this (function equality, tuples with more than 6 elements, other comparable values) share the same relevant characteristics, so previous discussions on function equality presumably apply equally well across the board here.

tl;dr arguably it's worth having `Array.get` and `(/)` return `Maybe` and then possibly offering an unsafe alternative that you could alias?

Max Goldstein

unread,
May 27, 2015, 8:50:45 AM5/27/15
to elm-d...@googlegroups.com
I'm against making mergeMany more complicated. It cannot be passed a signal of lists; therefore if it blows up it will blow up immediately. This is the same methodology we use with incoming ports that aren't wired: run-time, but instant. 95% of the time it's used with a literal list, and if you are so inclined we could probably check for the literal empty list at compile time. I'm not too worried about

evil = []
huh = Signal.mergeMany evil

Also, all the Json occurrences are in the native implementation, which defines a JS function called crash which does not end the program. Try: git grep "crash" -- *.elm

Max Goldstein

unread,
May 27, 2015, 8:53:57 AM5/27/15
to elm-d...@googlegroups.com
Division by zero is actually not a runtime error.

> 1 // 0
0 : Int
> 0 // 0
0 : Int
> 1 / 0
Infinity : Float
> 0 / 0
NaN : Float

I'm fine with this for floats. For integers... eh, I'd be okay throwing a warning in the docs for (//).

Janis Voigtländer

unread,
May 27, 2015, 10:48:01 AM5/27/15
to elm-d...@googlegroups.com

I agree with Max about Signal.mergeMany []. If this throws an error, it will do so during the signal graph initialization phase. Which is already almost as good as a compile time error. (In some sense, one might say it is a compile time error, seeing the life time of an Elm program as three phases: 1. compilation from Elm to JS, 2. static computation/“compilation” of a signal graph (with embedded JS) from JS, 3. running the signal graph.)

--

Reply all
Reply to author
Forward
Message has been deleted
0 new messages