Hello there. This is my first message to the elixir group. Thanks for the great language.
While I'm writing my code, I want to make functions to be safer. It's bad practice if a function accepts unexpected input and pass it further, and it blows in a completely different part of a system.
At first glance, I have pattern matching, but it's pretty limited. It becomes really powerful in conjunction with guards, so I can write a signature to match literally everything.
But they hard to re-use, If I have multiple functions operating with the same object. Yes, I can define a custom guard, but can I use pattern matching there? `Kernel.match?/2` doesn't work, so I'm limited with only guards in my custom guards.
Another thing that we have typespecs. It seems exactly what I'm looking for: you have a wide set of built-in types, and I can easily compose and reuse my own types. The problem with it, that it doesn't affect runtime. I know about static analyzer `dialyzer`, but I'm not sure it will catch all cases since it's a static check, not a runtime.
Let's assume a simple function, that wraps a value into a list:
@spec same(number()) :: [number()]
def same(number) do
[number]
end
I'm sure the `dialyzer` won't complain since a signature is valid. But what if I do: `same("abc")` ? What will prevent Elixir from returning a wrong type? I guess, nothing.
An example from a real life: I have a function, that accepts a custom shaped value (using tuples) and feeds it to a queue. Then, in the totally different part of the system, a consumer gets values from the queue. And when a wrong value was fed on the producer side, it blows on the consumer side. So I decided to put some constraints on the producer side to fail fast.
Yes, I could define a guard, but again, if I have a pretty complex type instead of the simple `number`, I had to duplicate the type defining: one for typespec, another is for a custom guard (which is limited, since I can't use pattern matching there).
Wouldn't it be cool, If we had a mechanism to assert a value to its type, in runtime? To avoid performance penalty we could enable it only for runtime. Is there a way right now to check whether a value corresponds to a type in runtime? Can I implement a custom macro to provide a good DSL for this? Is it helpful at all?
P.S. You may say, use structs and pattern matching would work in this case. But what if my type is better represented by a tuple: {atom(), pos_integer(), string()}. Converting it to a struct might complicate a way to work with the value.