In Haskell I frequently use “error” in development. first it’s a placeholder, able to take on any type, allowing me to see if my program type checks early on. Second, it’s kind of alike “asert” in that it will force a crash in an exceptional situation. I’d like to do something similar in elm. Is this possible? The two criteria are “something that takes on any type” and “something that crashes with an error message”
There is the added benefit that the compiler will not let you ship Debug.todo in production. If you compile with --optimize it will error if it sees any Debug usage.
This is not good, as it gets translated to a while loop (since it’s tail recursive).
If this code ever gets executed, the browser just freezes up.
If you really want a function that actually crashes in production, I’d use something like this:
crash : String -> a
crash msg =
crash (crashHelp msg)
crashHelp : a -> a
crashHelp a =
crashHelp (crashHelp a)
or this one, which only works because the compiler doesn’t properly optimize this yet:
crash : String -> a
crash x = x |> crash
But if you ever really want to actually use this in production, you should probably question your approach.
You can also incorporate one of the few edge cases where Elm still crashes. I know of two: Comparing functions that are not two references to the same function, for example (\x -> x) == (\x -> x), or dividing by zero in the modulus function, i.e. modBy 0 1. Put one of these into a let binding in the crash function. (You still need to write some kind of recursion to trick the type system. But this way, it crashes on the first call instead of producing a stack overflow.)
ˋDebug.crashˋ is the only way to get a message out with your crash, as far as I know. However, it can’t be used when compiling in production mode, so if you really want something like it in production, the message-less versions above are the only way.
You don’t want to crash, of course. But occasionally, Elm’s type system is not strong enough to make every impossible state unrepresentable. In that case, you might end up in a situation where (you think) you know that a certain case cannot come up, but you can’t express it in the types and therefore you have to handle it. I think there are three types of solutions:
You might return a Maybe and let the case bubble up to a place where you can handle it. But this is quite a heavy solution if the case is actually impossible.
You can decide to return some default value. If the case is impossible it doesn’t matter what you return. But if there is an error in your reasoning and the case is possible after all, instead of just crashing you’re now corrupting your data with this incorrect default value.
You can crash. I would argue that this is the best solution for actually impossible cases (because I’d rather crash than corrupt the data), but Elm does not provide a standard solution for that in production code. I guess the main reason is that one tends to get lazy and use this function even in cases where a better solution (for example a remodeling of the types to make the impossible state unrepresentable) exists.
By the way, Elm takes different approaches in the standard library: A lot of functions return Maybes. Division by zero returns a default value (namely zero). Modulo by zero crashes. (I really don’t understand why these two are inconsistent, if division by zero returns zero, modulo by zero should return the number itself.)
In production, knowing what crashes shouldn’t be necessary, since your crash should never happen there (and if it does, it will happen to someone else, so you won’t know anyway). In development, use Debug.todo.