To latch onto @al2o3cr’s example:
…you’d be better off using a property test in Elixir that simply asserts that costs
and revenue
must always be >=0 and that the result of profit
must never be greater than the revenue
parameter. That gives you a reasonable safety net that you are not writing something idiotic. (Though if you wanna get into the negative values, it gets a bit more involved. Still, IMO not a bad example.) And now you can move on with life.
IMO no strongly statically typed language can help you here because there’s no way for your compiler to know your expectations; summing two integers / floats is a valid operation. You’ll have to have a type for each thing and combine them only through methods but then again, you can do that in any language.
On the broader topic: strong static types will help you eliminate tests where you have to explicitly assert that data whose shape is not obvious (mish-mash of maps / structs / tuples / lists) and the functions working with that data act like you expect them to. And to make bad state a compiler error.
I can’t think of a better example right now but, code from a previous contract:
config :app, App.Repo,
ssl_opts: [
verify: :verify_peer,
cacertfile: Application.app_dir(:platform, ["priv", "cert", "digitalocean.pem"]),
server_name_indication: to_charlist(System.get_env("DATABASE_HOST"))
]
I lost count of the times people get such subtle configuration hierarchies wrong (especially HTTPoison’s!) and have prod spit out errors as a result – to the point of seriously considering writing a library to validate them (if I ever have the time and energy in this life that is ).
…And don’t even get me started on the various telemetry configs. That’s a dark forest if I ever seen one.
With Rust you can eliminate 95% of these problems by doing something like this:
enum SslVerify {
VerifyNone,
VerifyPeer {
cert_path: Path,
depth: u32,
},
}
fn ssl_verify_none() { SslVerify::None }
fn ssl_verify_peer(cert_path: String, depth: u32) {
SslVerify::VerifyPeer { cert_path: Path::new(cert_path), depth: depth }
}
And then pass that around wherever you need it. (NOTE: It’s possible to construct an invalid path in Rust of course, but the point here is that you will have some validation while constructing it.) And you can use the constructors to make sure no invalid config is constructed. Though the constructors pattern can be used in every language, but in this case (Rust) I am demonstrating that you can formulate a type that makes it impossible to have a bad state (minus a bad path but let’s not latch onto that; there are limits enforced by the C API to the Unix OS-es after all and that’s not the fault of the strongly strictly typed language).
To me, the biggest win we can score with the set-theoretic type system is finally putting these mish-mashes of keyword lists and primitive values to rest (though I am very sure that checking various dependencies configs is not in scope but this is what I’d write to use the system when it exists).
So to me, a strong static typing system will eliminate the need for me to manually test weird data shapes.
Thinking of it, a TL;DR would be “it will help us interface with Erlang libraries”, maybe.