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.

Reply via email to