Earlier quoted context omitted.
I see that language parroted all the time - "With thorough enough testing a dynamic language shouldn't be a problem", and I have never understood it. Arguing to build what is essentially a build-time type checker in the form of automated tests seems twice as cumbersome for half the benefit. Instead of building tests that check each branch of a program's types, why not use a language that forbids dynamic typing? You s…
> You should still have tests, but IMO tests that are just checking that a string is a string are The correct completion to this sentence is "irrelevant.", because that's not what anyone is proposing. The fact is, behavioral tests catch a lot of type errors even without intending to, and more to the point, if you test all the behavior you care about, then you don't care if there are type errors, because they only occ…
f = fn
{:ok, message} -> "It worked #{message}"
{:eror, message} -> "There was an error #{message}"
end
Running this iex(2)> f.({:ok, "yay"})
"It worked yay"
iex(3)> f.({:error, "oh no"})
** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/1
This kind of error isn't caught if all of the testing doesn't cover the error paths.Contrast with Scala, where using Either (which can be Left or Right):
def f(x: Either[String, String]) = x match {
case Right(x) => s"It worked $x"
case Left(x) => s"There was an error $x"
}
If for example one forgets a branch: scala> def f(x: Either[String, String]) = x match {
| case Right(x) => s"It worked $x"
| }
^
warning: match may not be exhaustive.
It would fail on the following input: Left(_)
Or to follow the Elixir tuple pattern more closely: scala> sealed trait Status
| case object Ok extends Status
| case object Error extends Status
trait Status
object Ok
object Error
scala> def f2(x: (Status, String)) = x match {
| case (Ok, msg: String) => s"It worked $msg"
| case (Error, msg: String) => s"There was an error $msg"
| }
def f2(x: (Status, String)): String
scala> def f2(x: (Status, String)) = x match {
| case (Ok, msg: String) => s"It worked $msg"
| }
^
warning: match may not be exhaustive.
It would fail on the following input: (Error, _)
def f2(x: (Status, String)): String
The typo also gives an obvious type error: scala> def f2(x: (Status, String)) = x match {
| case (Ok, msg: String) => s"It worked $msg"
| case (Eror, msg: String) => s"There was an error $msg"
| }
case (Eror, msg: String) => s"There was an error $msg"
^
On line 3: error: not found: value Eror
Caveat: still learning Elixir and my Scala is rusty, so there might be better ways of doing the above. :)