Even if we only check on the %Foo{...} and %Foo{x | ...} I would expect the
behaviour to raise instead of ignoring the field value was you proposed.

And it still makes me think that you should probably wrap all of your
struct creation and modification in a function that does this kind of
validation. Guard validations are limited, you wouldn't even be able to
validate that some key must be given a struct, which I would say is a
fairly common use case.



*José Valimwww.plataformatec.com.br
<http://www.plataformatec.com.br/>Founder and Director of R&D*

On Wed, Oct 4, 2017 at 12:00 PM, Maciej Kaszubowski <
[email protected]> wrote:

> Thank you for the comment José.
>
> Sure, you're completely right that checking this every time is quite
> problematic and not intuitive. That's why I think the check should only be
> performed when creating the struct, not when matching on %Foo{}. I think
> that explaining properly it in the docs would solve the problems of too
> high expectations.
>
> And while I agree that having guards in the function should be used when
> possible, in my opinion, having that validation when creating the struct
> can be quite useful.
>
> There are multiple use cases when I think this will be really nice. For
> example, when creating a JSON api, we could create a struct for the
> expected response, adding the type validations for the fields. Using the
> fact that structs are in fact maps, we can easily convert the struct to the
> JSON response and have more confidence that the types are correct. This is
> a huge advantage compared to using plain maps for views. You could of
> course use guards in function in your view, but it will be really annoying
> for JSONs with multiple fields.
>
> Another example is using domain models in your application (but *not *a
> database schemas). Imagine model with 10 fields. To create the struct, we
> could just create and use Foo.new() function, but creating a function with
> 10 arguments and 10 guard clauses will be neither clear nor readable. And
> because there's no way to disable creating the struct manually, we cannot
> be sure that everyone will use `Foo.new()`. Instead, we can just create a
> typed struct and have the types validated every time we create it. Someone
> could still have invalid struct by  updating the fields, but I think this
> is a reasonable compromise.
>
> I am quite confident that this behaviour will be easy to understand,
> especially because it's quite similar to the way @enforce_keys attribute
> works.
>
>
> What do you think?
>
>
> Cheers,
> Maciej
>
> 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/5085a991-3323-4d2e-bbac-
> 135278b0e822%40googlegroups.com
> <https://groups.google.com/d/msgid/elixir-lang-core/5085a991-3323-4d2e-bbac-135278b0e822%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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/CAGnRm4KGCs3Kz0aTMepB4QDM6TFnhnQGNu5UD1BEGC8OtcBV3g%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to