-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type validation via when
clause in functions
#2
Comments
continuation the discussion from #1 with @OvermindDL1 I think types should be checked by dialyzer, especially since there is no way for checking it at compile time by macros. The best we can do, is to properly generate typespecs for each case (which is not being done yet) and for each constructor. I also think, the most important features of this lib are it's compile-time warnings. I'm afraid adding a feature that would work only in some cases will bring more harm than good (the Being explicit should be the default, and this is one of Elixir's goals. That's why user can always define their own constructors which will add things like function gaurds and the user will have the notion that functions run at run-time so there will not expect compile-time warnings in this case. That explicitness is also why I'm currently thinking about making the dynamically built constructors (based on union tag's name) to be disabled by default, and adding more explicit constructor (also a macro) that would work like this: defmodule Result do
use DiscUnion
@type value() :: any()
@type error_msg :: String.t
@type last_value :: any()
defunion Ok in value | Error in last_value * error_msg
end
defmodule X do
require Result
def x do
Result.c Ok, 123
Result.c! Error, 0, "FUBAR"
end
end The difference between |
Hmm I like the explicit constructor, still think a 'when' validation would work well on them to enforce that the right information is stuffed into the type. But I am liking how that syntax works there. I might recommend trying to enforce And yeah, I am a fan of tagged tuples over map/structs myself, but I'm an old erlang programmer myself. ^.^ |
The biggest problem I have with the guards idea is this:
This mixed validation is where I have mixed feelings (pun intended 😉).
That's a good idea! Shorter to type and might can be handy someday.
Here we have both! 😄 I will leave this open, because I'm not 100% sure about this yet. Even @josevalim was asking about this in elixir-lang/elixir#925. |
I personally like the mixed validation, as this is a dynamically typed language there is no way to enforce types internally at compile-time, so I take whatever and everything that I can get. :-)
Ooo, a couple libraries I've not not come across yet, thanks!
Fascinating read, interesting to see that he and I are on the same page. What I would like is that the constructors for the types had |
Probably won't happen. Better check out j14159/mlfe, but they will have one big problem: static typing on messages from outside.
Except you wouldn't! Not unless macro constructors are removed, and I actually think they have the biggest value here - more compile time warnings! Look here, and let's assume that defmodule Result do # same as above
...
end
defmodule X do
def x do
val = 1
Result.c! Ok, val # This is a function call. Once evaluated, returns either
# a %Result{case: {Ok, 1}} struct or raises an error if case
# tag or param are not valid - param is validated via guards
Result.c Ok, val # This is equivalent to typing %Result{case: {Ok, val}}
# This is a macro, so the struct is put in place of the call
# during compilation. So any `when` gaurd clause could not
# be ran cause you cant put guards into a struct and the
# value of `val` is known only at run-time
# now let's make a typo!
Result.c! Eror, :wat # Would rise an error only at run-time
Result.c Eror, :wat # Would rise straight away at compile-time.
end
end
You can still achieve that by creating your own constructor functions!* defmodule Result do
use DiscUnion, dyn_constructors: false
defunion Ok in any | Error in any
def ok(any), :do c Ok, any
def error(reason) when is_atom(reason) do
c Error, reason
end
end *Once this
That's why I think that user-built constructors are the way to go here. The most frequent use cases I see for discriminated union is creating collections of identifiers. Like in here: x4lldux/ex_json_schema@bff986d/lib/ex_json_schema/validator/error.ex or for creating a collection of CQRS/ES events. With that many variants simple human error will be more frequent - like typos or case omissions - than a wrongly passed case param. At least this is what I believe. For now, I still need to implement the |
That actually seems like a pretty easy issue to resolve, some kind of 'cast'er that returns a |
Sent too soon, continuing...
I would honestly be fine with removing them from match guards. Match guards get integrated into a single function with a head # From this:
def func(Result.c(Ok, val)), do: nil
# To this:
defsomething func(Result.c(Ok, val)), do: nil
Yeah that would be the big use-case for me as well, I have a lot of areas where enforced checking like this would save me a lot of hassle. |
Don't quite understand your message here. Can you rephrase that? By those "macro constructors" I meant those autogenerated macros for each union,
I think it's not. Type check they implemented, infers based on what it can find in the code. Now, what would happen to messages sent from outside - say a :DOWN message sent by the BEAM; typechecker has no way of knowing this message can be sent. They even say it them selfs:
|
No description provided.
The text was updated successfully, but these errors were encountered: