A simple example of this is checking the size of an array or matrix, then calling into recursive functions where you will look up values at particular indexes that you know are not out of bounds.
Examples:
- imeckler/iterator/Iterator.elm:82 (Using Array.get with an index known to be less than the array's length)
- jvoigtlaender/elm-gauss/src/Gauss.elm:42 (Using Matrix.get with an index known to exist)
- mgold/elm-nonempty-list/src/List/Nonempty.elm
- prozacchiwawa/elm-keccak/src/Keccak.elm:134
A more complex example is when implementing a dynamic programming algorithm or similar, and storing the computed values in a Dict (or Array, or Matrix). If your algorithm is correctly implemented, then the values of other nodes needed to compute the current node will already have been computed and inserted into the Dict. Using Debug.crash
is desirable when developing because it is a critical implementation mistake if the crash is ever encountered, and is possibly desirable in production so that a request to report the issue to the package author can be provided.
Examples:
- avh4/elm-diff/src/Diff.elm:119
- jinjor/elm-diff/src/Diff.elm
- (I was surprised there weren't other examples of this)
There are more complex examples of implementing an algorithm which can be proven to never hit certain cases, but writing them in Elm requires handling the impossible case. Using Debug.crash
is desirable when developing because it is a critical implementation mistake if the crash is ever encountered, and is possibly desirable in production so that a request to report the issue to the package author can be provided.
Examples:
- elm-community/graph/src/Graph.elm
- ("Graph.computeEdgeDiff: Collected two removals for the same edge. This is an error in the implementation of Graph and you should file a bug report!")
- ("dfsTree: There can't be more than one DFS tree. This invariant is violated, please report this bug.")
- ("Graph.ignorePath: No algorithm should ever pass an empty path into this BfsNodeVisitor.")
- ("Graph.heightLevels: Could not get a node of a graph which should be there by invariants. Please file a bug report!")
- ("Graph.heightLevels: Reached a branch which is impossible by invariants. Please file a bug report!")
- elm-community/intdict/src/IntDict.elm:720
- ("IntDict.uniteWith: mergeWith was called with 2 Nothings. This is a bug in the implementation, please file a bug report!")
- eskimoblood/elm-parametric-surface/src/Mesh.elm:124
- folkertdev/elm-deque/src/Deque.elm:240
- mmetcalfe/elm-random-distributions/src/Random/Distributions.elm:285
- ("The list normal ziggurat tables was not of length n")
- opensolid/geometry/src/OpenSolid/Polygon2d/Monotone.elm:513
- pdamoc/elm-hashids/src/Hashids.elm:111
- r-k-b/elm-interval/src/Union.elm:239
- s6o/elm-simplify/src/Simplify.elm:99
- ("Simplification using Ramer-Douglas-Peucker algorithm")
- SelectricSimian/elm-constructive/src/Constructive/Action.elm:174
- ("If Elm's type system is sound, then it is impossible to construct a value of type
Never
. Therefore, it should be impossible for this branch to be executed.")
- ("If Elm's type system is sound, then it is impossible to construct a value of type
- spisemisu/elm-merkletree/src/Merkle.elm
- zwilias/elm-tree/src/Tree/TwoThree.elm
- ("Empty leaf at this point means the invariants were not maintainted.")
- ("Found smaller or equal item in right hand branch. Invariants not maintained.")
- ("We know it's there, just remove it, you can't tell me it wasn't found.")
A program's model has Maybe x
, and the program's init performs a Cmd
that populates it with a Just
value, and then initiates Msgs that require a Just to process.
Better solution: processing the messages should do nothing instead of crashing if there is Nothing
.
However, when developing, the crash can be desirable so that it's obvious what went wrong.
Examples:
- athanclark/elm-duration/src/Duration.elm
- rtfeldman/html-test-runner/src/Test/Runner/Html/App.elm:66
- (not an exhaustive list)
Better solution: functions that require a non-empty list should instead take a first item and "rest" items.
However, this makes it annoying to use those APIs with literal lists:
Examples:
- avh4/elm-debug-controls/src/Debug/Control.elm:219
- elm-community/random-extra/src/Random/Extra.elm:164
- vilterp/elm-diagrams/Diagrams/Actions.elm:88
- vilterp/elm-diagrams/Diagrams/Bezier.elm:31
- (not an exhaustive list)
Better solution: the function in question should return Result String a
instead of a
.
However, this can make the API tedious to work with for most common uses.
Examples:
- drathier/elm-graph/src/Graph/Random.elm:34 (crashes if provided a cyclic graph)
Examples:
- Fresheyeball/elm-restrict-number/Convert.elm (converting Prime to Whole, Positive to Natural, etc)
- JoeyEremondi/elm-MultiDimArray/Array/MultiDim.elm:154
- JoeyEremondi/elm-SafeLists/List/Safe.elm
- (not an exhaustive list, but I was surprised there weren't many examples of this)
Re: "Looking up an index that is known to exist" - the nonempty list one seems reasonable, but I would not call the other three valid.
The author could have used
Array.foldl
instead ofDebug.crash
, and it would likely have much better performance.The author chose to use a
Matrix
package which does not exposefold
, and they're usingDebug.crash
here to implement their ownfold
on top of aMatrix
that doesn't support it. Instead, they could have chosen aMatrix
package that did offerfold
, or forked the one they were using to addfold
themselves.The author translated C code directly to Elm, crashes and all. They're using
Array
to represent mutable memory slots, like what polyfilled Web Assembly does. I'd like to think that ifDebug.crash
were not available, the author would have been drawn to think through their approach more.