Closed OvermindDL1 closed 8 years ago
As described in readme, there are no type checking. Whatever you put there will be for user documentation purely. Putting a when
clause would be counter-intuitive in moste cases, because the aim is to have compile time warnings, but it's impossible/extremely- hard to determine the type at compile time in Elixir. On the other hand, you can create a custom constructor which will contain the when
clause you want - but since this will be a function, it will report mistakes/errors only at run-time.
I'm working on fully generated typespecs, for each clause, macros and functions generated by defunion
, do dialyzer can caught such bugs as you mentioned.
As described in readme, there are no type checking.
That is why I had the idea to add some ability to add a when
clause to enforce some at-least run-time type checking on the constructors. :-)
Whatever you put there will be for user documentation purely.
The example user documentation is not matching the exampleusage is what I was talking about there. :-)
The example user documentation is not matching the exampleusage is what I was talking about there. :-) You mean something like this:
defunion :ok in value: any | :error in msg: String.t
?
I'm thinking about that syntax but haven't made my mind yet. Even in current implementation you can still have typespecs and description both:
@type value :: any
@type msg :: String.t
defunion :ok in value | :error in msg
As in the example where :error
is documented as being a String.t
, but a few lines lower :error
is only storing atoms. :-)
As for a when syntax, hmm... I know that |
is more how functional languages define different types but ,
might be more idiomatic elixir (regardless of if better or not)? As for when
's... Maybe:
defmodule Result do
defunion do # This also leaves open future 'option' opportunities to be added to the defunion call
@spec ok(any())
def ok(value)
@spec error(String.t)
def error(msg) when is_binary(msg)
end
end
Or something like that, just in a way where each union subtype defines what it can store in an elixir function-like head form, thus the number of arguments it takes as well as the 'when' is easier to read for most Elixir programmers. I'm wondering if it would be better to have defunion make its own module instead of injecting into the parent, if so it could be like:
defunion Result do
def ok(value)
def error(msg) when is_binary(msg)
end
And I wonder if there is a way to 'enhance' the types, hmm... Let me play with syntax...
defunion Result, ok_type: :any, error_type: :any do # :any would be a 'key' type of no 'when' clause by default
def ok(value)
def error(reason)
end
# Then elsewhere like my own custom modules:
defmodule Stuff do
defunion Result, refines: Result, ok_type: is_map(&1), error_type: is_binary(&1) # No `do` since we are refining it, though we could add a `do` if we wanted to add more cases or helpers perhaps?
end
This way my 'Stuff' module uses its refined Stuff.Result
type for its return types. You can easily implement this by making a hidden function on every union that defines its data that another union could then bring in and refine or so. Mostly thinking out-loud but such a refinable union would be quite nice, especially with proper when
's being put on and all for additional run-time checks. I checked the syntax and it works. Hmm, ideas...
As in the example where :error is documented as being a String.t, but a few lines lower :error is only storing atoms. :-)
I'll add a comment to remind readers that types are not checked.
As for a when syntax, hmm... I know that | is more how functional languages define different types but , might be more idiomatic elixir (regardless of if better or not)?
I was experimenting with using comma, but it turned out that elixir was interpretation as a arguments separator for macro. That would mean there should be defunion/1
, defunion/2
, defunion/3
, etc.. Also, the |
symbol is already used for defining the same meaning in typespecs! It's used to define different possible clauses, which is basically what we are doing here. I'm not bent on this syntax, but is composites nicely with current typespecs syntax and syntax of other functional languages that do have summation types.
def ok(value)
Reusing def
is definitely a bad idea. It has a strict meaning, and giving it another one, in context of defunion
, would only confuse developers. This syntax also doesn't let using ModuleNames
as case tags.
I'm wondering if it would be better to have defunion make its own module instead of injecting into the parent
That would require a defunion/2
be part of the Kernel
module, which is always imported.
Regarding types and when
gaurds, I've moved the discussion to issue #2, since this issue is mainly about mismatch in the example.
The naming of def
was just an example, it could be named anything, just too lazy to type out something longer. ^.^
Fixed in 9fe3c24d8a40e2a3d7cf561f36e88cec6381b25a.
I noticed in the README.md the line in the code of
defunion :ok in any | :error in String.t
, and yet the:error
case is later matches to atoms, not strings. Does this library put type declarations as appropriate? Is there a way for us to specifywhen
clauses on the constructor so we can enforce types?