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.

Reply via email to