I think the behaviour I'm proposing is in fact to raise, rather then to
ignore the field. The code I showed produces this result when the field
doesn't pass the check:
iex(2)> %MyStruct{name: 5}
** (ArgumentError) The following fields didn't match the guards: struct
MyStruct: [{:name, :is_binary, 5}]
expanding struct: MyStruct.__struct__/1
Wrapping the struct creation in a function is a good way (because it helps
abstracting the struct implementation), but the problem is that this
doesn't prevent using %Foo{key: value} syntax directly which can lead to
bugs. And while guards are in fact limited, this is supposed to be a
minimal (and optional) check for the data format, not a full blown
validation.
And when it comes to limits, the proposed solution can be quite universal,
actually. Because I use Kernel.apply/3, we could allow to pass arbitrary
arguments inside @guards, for example:
@guards [active: {:in, [true, false]}]
@guards [age: {:>, [18]}]
Then,
case @guards[key] do
{fun, args} ->
if apply(Kernel, fun, [value | args]) do
# ok
else
# add error
end
# ...
end
While the syntax can probably be improved, this is quite flexible solution
to validate the types.
If this doesn't convince you, I guess I trust that you're correct and this
is the right decision :) Thanks!
W dniu środa, 4 października 2017 11:08:17 UTC+2 użytkownik Maciej
Kaszubowski napisał:
>
> Hello,
>
> *Proposed feature*
>
> I'd like to propose another improvement on structs. Inspired by
> @enforce_keys,
> I'd like to propose adding @guards which can help to validate the types of
> the fields in the struct.
>
> Example usage:
>
> defmodule MyStruct do
> @guards [name: :is_binary]
> defstruct [:name]
> end
>
>
> which will fail if the given condition is not satisfied:
>
> iex(2)> %MyStruct{name: 5}
> ** (ArgumentError) The following fields didn't match the guards: struct
> MyStruct: [{:name, :is_binary, 5}]
> expanding struct: MyStruct.__struct__/1
>
>
> *Notes*
>
> - As the example shows, the behaviour will be similar to @enforce_keys
> - it will be checked only when creating the struct, not when updating
> - Using module attribute allows to keep this optional and allows to
> keep backwards compatibility
>
> *Possible implementation*
>
> With https://hexdocs.pm/elixir/master/guards.html and Kernel.apply/3, we
> can modify existing def __struct__(kv) from Kernel:
>
>
> def __struct__(kv) do
> {map, errors} =
> Enum.reduce(kv, {@struct, {[], @enforce_keys}}, fn {key, val}, {map,
> {type_errors, key_errors}} ->
>
> guard = @guards[key]
> if guard && apply(Kernel, guard, [val]) do
> {Map.replace!(map, key, val), {type_errors,
> List.delete(key_errors, key)}}
> else
> {
> Map.replace!(map, key, val),
> {[{key, guard, val} | type_errors], List.delete(key_errors, key)}
> }
> end
>
> end)
> case errors do
> {[], []} -> map
> {types, []} ->
> raise ArgumentError, "The following fields didn't match the guards:
> " <>
> "struct #{inspect __MODULE__}: #{inspect types}"
> end
> end
>
>
>
> This, of course, needs style improvements (and validation of required
> fields which is currently removed for the sake of clarity), but this is
> only a proof of concept to verify that the implementation is possible and
> quite easy.
>
> *Why not use @type?*
>
> While it would be cool to be able to verify the types based on typespecs,
> it would be harder because I think not all types can be easily validated.
> The suggested approach with guards will be feel more familiar because we
> can already do this for functions. Adding guard validation for struct
> fields feels like reasonable step.
>
> *What do you think?*
>
> I'd be happy to start working on this feature, but I wanted to know what
> do you all think about this.
>
>
> Cheers,
> Maciej
>
>
--
You received this message because you are subscribed to the Google Groups
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/elixir-lang-core/21c292a5-1f09-473c-a489-a275ff5f892e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.