Keyword.validate! <https://hexdocs.pm/elixir/Keyword.html#validate/2> is
meant to solve part of the option validation, except typing.

NimbleOptions can solve typing but ideally I would try to embed the basic
type validation in the type system.

So in the long term, I would say type system + Keyword.validate!.

On Fri, Oct 28, 2022 at 7:05 PM Zach Daniel <zachary.s.dan...@gmail.com>
wrote:

> Couldn’t anyone who wants to do something like this just use a tool like
> nimble_options (at least in the near to mid term)? That’s what I do.
> https://github.com/dashbitco/nimble_options
>
>
> On Fri, Oct 28 2022 at 12:35 PM, Wiebe-Marten Wijnja <w...@resilia.nl>
> wrote:
>
>> Thank you for starting this interesting discussion!
>>
>> While I don't think the suggested solution (introducing special pattern
>> matching syntax) is viable, for the reasons already mentioned by others,
>> I do think the problem itself warrants further consideration.
>>
>> Currently, handling keyword arguments is done in an ad-hoc fashion.
>> Approaches between different codebases and even between different parts
>> of the same codebase vary significantly.
>> Especially w.r.t. error handling.
>> Even in Elixir's own codebase this is apparent. Some (non-exhaustive)
>> examples:
>> - Passing wrong options to `if` raises an ArgumentError with the text
>> "invalid or duplicate keys for if, only "do" and an optional "else" are
>> permitted"
>> - Passing wrong options to `defimpl` raises an ArgumentError with the
>> text "unknown options given to defimpl, got: [foo: 10, bar: 20]"
>> - Passing wrong options to `for` raises a CompileError with the text
>> "unsupported option :foo given to for"
>> - Passing wrong options to `inspect` ignores the option(s) silently.
>> - Passing wrong options to `GenServer.start_link` ignores the option(s)
>> silently.
>>
>> Other differences are between whether only keyword lists are accepted, or
>> maps with atom keys also, or possibly anything implementing the `Access`
>> protocol.
>> And in some places the options are used to create a special struct
>> representing the parsed options, which is allowed to be passed as well
>> directly.
>>
>> This makes me think that we might want to look into standardizing:
>> - How to indicate which options are mandatory and which options have
>> defaults.
>> - What kind of exception is raised when incorrect values are passed (and
>> with what message).
>> - By default raise whenever unrecognized options are passed; the
>> alternative of ignoring unrecognized options as an explicit opt-in choice.
>>
>> I think we could introduce a macro that embeds the code to do these
>> things and turn the result into a map inside the function where it is
>> called.
>> For the reason mentioned by José before (supporting multiple function
>> clauses with different pattern matching and defaults)
>> it makes more sense to call this macro in the function body rather than
>> embellish the function head with some special form.
>> What I haven't been able to figure out yet is how to call this macro
>> (`parse_options`?), or in which module in Elixir core it should live.
>> (`Keyword`? Or in a new `Option` module?)
>>
>> I haven't written a proof-of-concept yet but I am pretty sure that it is
>> possible to write an implementation that needs to traverse the list --or
>> map--
>> that is passed in to the function only once. (Stopping earlier when the
>> number of keys inside do not match.)
>> This should be performant *enough* for general usage.
>> If there is a problem, I think that raising an ArgumentError (but with a
>> different error message detailing what options are missing or unrecognized)
>> might be the clearest way to indicate to the caller that they are using
>> the function incorrectly.
>>
>> The diligent reader might notice that there certainly is some overlap
>> between this new macro and initializing a struct with enforced keys.
>>
>>
>> ~Marten / Qqwy
>>
>>
>> On 28-10-2022 16:20, Jake Wood wrote:
>>
>> So the original proposal here is for introducing a named parameter
>> syntax. The reason I like named parameters is b/c the order of parameters
>> doesn't matter – when they do matter it's easy for refactoring to introduce
>> hard to catch bugs. Pattern matching has been proposed as the idiomatic way
>> to achieve argument order not mattering. If I understand correctly, the
>> recommendation is to stuff arguments into a map just before a function call
>> that itself immediately destructures them. While this approach does address
>> my primary concern (ie parameter order), it has to be slower, right? I can
>> imagine this having a non-trivial effect in a pipeline on a hot-path.
>>
>> So the question for me, really, is how much quicker is passing ordered
>> arguments vs creating then destructuring a map? If it's negligible then
>> it's negligble, but if it's not then it would be nice to have an
>> alternative.
>>
>> - Jake
>>
>> On Friday, October 28, 2022 at 9:47:31 AM UTC-4 José Valim wrote:
>>
>>> > Is this an expensive pattern because it generates a map only for the
>>> next function to extract the keys and ignore the map?
>>>
>>> It depends on what you are comparing it with. Compared to simply passing
>>> arguments, it is likely slower. Compared to keyword lists, likely faster.
>>>
>>> On Fri, Oct 28, 2022 at 3:41 PM Brandon Gillespie <bra...@cold.org>
>>> wrote:
>>>
>>>> Fair enough :)
>>>>
>>>> If I understand what you are saying: they are all maps because the
>>>> source data comes from a map, and it's the method of extracting data from
>>>> the map that differs (the algorithm), not the inherent nature of a map
>>>> itself.
>>>>
>>>> I agree, and apologize for the mistaken assertion.
>>>>
>>>> However, what I didn't benchmark as i think about it, is what I often
>>>> will see, which is the creation of a map simply to pass arguments — and
>>>> this is more relevant to the request/need. The example was based on
>>>> existing structs/maps and not creating them at each function call time.
>>>>
>>>> Instead, for example:
>>>>
>>>>        def do_a_thing(%{key2: value2, key1: value1}) do ...
>>>>
>>>> I think it's becoming a common pattern to then construct the maps as
>>>> part of the call, ala:
>>>>
>>>>        do_a_thing(%{key1: 10, key2: 20})
>>>>
>>>> Is this an expensive pattern because it generates a map only for the
>>>> next function to extract the keys and ignore the map?
>>>>
>>>> -Brandon
>>>>
>>>>
>>>> On 10/28/22 12:37 AM, José Valim wrote:
>>>>
>>>>
>>>>
>>>>> 1.79 times, as I read it, not 1.79us. And of course benchmarks being
>>>>>> highly subjective, now that I retooled it it's at 2.12x slower (see notes
>>>>>> at the very bottom for likely reasons why).
>>>>>>
>>>>> Correct. What I did is to take a reference value of 1us and multiplied
>>>> it by 1.79, to say that at this scale those numbers likely won't matter.
>>>>
>>>>> The gist includes three scenarios:
>>>>>>
>>>>> Thanks for sharing. I won't go deep into this, as requested, but I
>>>> want to point out that the conclusion "maps are slower (significantly
>>>> enough to avoid)" is still incorrect for the benchmarks above.
>>>>
>>>> All of those benchmarks are using map patterns because both map.field
>>>> and Map.get are also pattern matching on maps.
>>>>
>>>> map.field is equivalent to:
>>>>
>>>> case map do
>>>>   %{field: value} -> value
>>>>   %{} -> :erlang.error(:badkey)
>>>>   _ -> :erlang.error(:badmap)
>>>> end
>>>>
>>>> Map.get/2 is equivalent to:
>>>>
>>>> case map do
>>>>   %{field: value} -> value
>>>>   %{} -> nil
>>>> end
>>>>
>>>> To further drive this point home, you could rewrite the map_get one as:
>>>>
>>>>   def map_get(engine) do
>>>>     map_get_take(engine.persist, engine, @take_keys, [])
>>>>   end
>>>>
>>>>   defp map_get_take(engine, persist, [a | rest], out) do
>>>>     case {engine, persist} do
>>>>       {%{^a => value}, %{^a => value}} ->
>>>>         map_get_take(engine, persist, rest, [{a, value} | out])
>>>>
>>>>       _ ->
>>>>         map_get_take(engine, persist, rest, out)
>>>>     end
>>>>   end
>>>>
>>>>   defp map_get_take(_, _, [], out), do: out
>>>>
>>>> And the numbers likely won't matter or be roughly the same. The point
>>>> is: you are effectively benchmarking different algorithms and not the
>>>> difference between map_get or map_pattern.
>>>>
>>>> I am only calling this out because I want to be sure no one will have
>>>> "maps are slower (significantly enough to avoid)" as a take away from this
>>>> discussion.
>>>>
>>>> > What if a syntax for matching on keyword lists that allowed for items
>>>> in any position was added to Elixir? Something like (just shooting from the
>>>> hip) `[…foo: bar]` ? Then you could have your cake and eat it too, right?
>>>>
>>>> Valid patterns and guards are dictated by the VM. We can't compile
>>>> keyword lists lookups to any valid pattern matching and I would be
>>>> skeptical about proposing such because we should avoid adding linear
>>>> lookups to patterns.
>>>>
>>>> It is worth taking a step back. It is not only about asking "can we
>>>> have this feature?". But also asking (at least) if the feature plays well
>>>> with the other constructs in the language and if we can efficiently
>>>> implement it (and I believe the answer is no to both).
>>>> --
>>>> You received this message because you are subscribed to a topic in the
>>>> Google Groups "elixir-lang-core" group.
>>>> To unsubscribe from this topic, visit
>>>> https://groups.google.com/d/topic/elixir-lang-core/Dbl6CL5TU5A/unsubscribe
>>>> .
>>>> To unsubscribe from this group and all its topics, send an email to
>>>> elixir-lang-co...@googlegroups.com.
>>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4L37yu8KVbhuM0gNkVYOzCeoXaKzTBk4aY4OLLRdgRRLg%40mail.gmail.com
>>>> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4L37yu8KVbhuM0gNkVYOzCeoXaKzTBk4aY4OLLRdgRRLg%40mail.gmail.com?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>>> --
>>>>
>>> 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 elixir-lang-co...@googlegroups.com.
>>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/elixir-lang-core/9f60ba0c-8403-e93f-d5fb-b3f55df88d14%40cold.org
>>>> <https://groups.google.com/d/msgid/elixir-lang-core/9f60ba0c-8403-e93f-d5fb-b3f55df88d14%40cold.org?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>> --
>> 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 elixir-lang-core+unsubscr...@googlegroups.com.
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/elixir-lang-core/01432858-e854-4747-921a-230e6bbd7489n%40googlegroups.com
>> <https://groups.google.com/d/msgid/elixir-lang-core/01432858-e854-4747-921a-230e6bbd7489n%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>>
>> --
>> 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 elixir-lang-core+unsubscr...@googlegroups.com.
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/elixir-lang-core/50a4057e-1d53-77fe-6cf5-1d7804f32b8b%40resilia.nl
>> <https://groups.google.com/d/msgid/elixir-lang-core/50a4057e-1d53-77fe-6cf5-1d7804f32b8b%40resilia.nl?utm_medium=email&utm_source=footer>
>> .
>>
> --
> 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 elixir-lang-core+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/l9squzpm.82ffe77f-1d5b-4c9f-9adf-83a8ed0cf0e8%40we.are.superhuman.com
> <https://groups.google.com/d/msgid/elixir-lang-core/l9squzpm.82ffe77f-1d5b-4c9f-9adf-83a8ed0cf0e8%40we.are.superhuman.com?utm_medium=email&utm_source=footer>
> .
>

-- 
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 elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4KA%2BzATOV%2BW7AQOXeKKkxxg%2BcCfOv-nztsPS5JRu6ezdg%40mail.gmail.com.

Reply via email to