As a quick addendum: Scratch the "because it seems much more useful if
we could use it for any structs."-part of my previous message.
I mistakenly remembered that `get_in` allows you to fetch keys from
structs that do not themselves follow the `Access` behaviour, which is
nonsense :-).

~Marten/Qqwy

On 09-02-2020 13:24, Wiebe-Marten Wijnja wrote:
>
> The underlying problem to me seems that in this example `nil` is used
> both as a /default/ to be returned when nothing is found and as an
> /actual value/ in one of the data structures.
>
> I do not think that finding a way to treating them as 'the same' is a
> good solution to the problem, because:
>
> - It is difficult to do this in a backwards-compatible way.
> - As José highlighted, there is a difference between `get_in` and the
> other Access-based `*_in`-calls.
> - It becomes more difficult to reason about the code because the
> difference between the two approaches is very subtle.
>
> Instead, I think that rather than treating this symptom (frustration
> at seemingly inconsistent behavior),
> we should tackle the underlying cause (the behavior being consistent
> but confusing):
>
>
> If we'd have an alternative to `get_in` that does not rely on `nil`
> being used as default 'nothing found' value, then the difference
> between the examples becomes immediately apparent.
>
> We e.g. could introduce something named e.g. `fetch_in` that makes a
> clear distinction between values that are not in the nested collection
> vs values (like 'nil') that are there:
>
> ```
>
> iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}
> iex> fetch_in(users, ["unknown", :age])
> :error
>
> iex> %{"items" => ["desired_value"]} |> fetch_in(["items", Access.at(0)])
> {:ok, "desired_value"}
>
> # This is the important difference
> # It is clear here that `nil` /is there/ rather than the default being
> returned.
> iex> %{"items" => nil} |> fetch_in(["items"])
> {:ok, nil}
>
> # Therefore, it now makes sense to the programmer
> # that an error is raised here
> iex> %{"items" => nil} |> fetch_in(["items", Access.at(0)])
> ** (RuntimeError) Access.at/1 expected a list, got: nil
>     (elixir) lib/access.ex:663: Access.at/4
>
> ```
>
>
> `fetch_in` is the most descriptive name that I thought of just now
> because of its return type being similar to `Access.fetch` (just like
> the other `*_in` functions).
> However, since `Access.fetch` depends on struct-modules overloading
> the behaviour, we might want to search for yet another name, because
> it seems much more useful if we could use it for any structs.
>
> Another problem with introducing `fetch_in` is that it adds a new
> function to Kernel.
>
> Another approach (which would also tackle the aforementioned issue of
> wanting to use the new function on structs that do not overload
> `Access.fetch`) would be to introduce an option as third parameter (or
> keyword parameter?)
> for `get_in` that would switch to explicit, ok/error-tuple-based,
> retrieval.
>
>
> As for actually solving Greg's practical problem at hand: If you want
> to treat explicit `nil`'s the same way as 'the key does not exist',
> then what about removing any fields that point to a `nil` value before
> performing your '`get_in` and friends'-based validation?
>
>
> ~Marten/Qqwy
>
> On 09-02-2020 09:01, José Valim wrote:
>> We are open to a mechanism that makes this possible but I am afraid
>> we haven't found one yet. path_expression doesn't help, because
>> again, it makes you think it is a general mechanism but it applies
>> only to get_in and not the other functions. All other functions do
>> not work with nil - so even in terms of inconsistency I am more
>> inclined to think get_in should always fail on nil and not the opposite.
>>
>> I am sorry but I cannot provide further guidance on this because I am
>> myself not sure what the solution is. If others have suggestions, we
>> will be glad to hear them.
>>
>> On Sun, Feb 9, 2020 at 2:18 AM Greg Vaughn <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>>     On Feb 8, 2020, at 6:34 PM, José Valim <[email protected]
>>     <mailto:[email protected]>> wrote:
>>     >
>>     > Also, I should have asked this sooner, but can't the complex
>>     path that you are writing be easily expressed with pattern matching?
>>
>>     Can your use cases use pattern matching too?
>>
>>     Since you asked, my primary use of Kernel.get_in is when I have
>>     untrusted json at the edge of my system. I have multiple sources
>>     that have to be mapped to a common internal struct/schema. The
>>     first step is to look for the equivalent of all the keys we care
>>     about and create a map with known key names. Then we go through
>>     an Ecto changeset for validation and further processing.
>>
>>     I look for, let's guess, about 15 fields from each of these json
>>     payloads. I'd have to pattern match 15 times with an if
>>     statement, because if I have 14 real values but the path through
>>     the json of one of them is not present, I still want to go
>>     through the Ecto validation logic because that one key that is
>>     missing might not be critical to our business logic. Since that
>>     logic is in the next innermost layer, I don't wish to code it
>>     into this outer layer that just tries to pull what it can out of
>>     the untrusted json.
>>
>>     I am open to naming concerns. I do rather like the #{name} vs.
>>     #{name}! approach to highlight the inconsistency in the existing
>>     get_in behavior. I think we could call it `path_expression` which
>>     is a term used in object oriented databases and in other
>>     languages, though it seems long. You said to take modifying
>>     `get_in` off the table from consideration, but I think it leads
>>     naturally to `get_in` vs. `get_in!`.
>>
>>     I can accept if the decision of the core team is that my use case
>>     is an outlier and I should write my own module to handle this.
>>     I'd still hate leaving the inconsistent behavior of get_in in the
>>     standard library, but I'll adapt and move on.
>>
>>     -Greg Vaughn
>>
>>     -- 
>>     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]
>>     <mailto:elixir-lang-core%[email protected]>.
>>     To view this discussion on the web visit
>>     
>> https://groups.google.com/d/msgid/elixir-lang-core/B090C168-E959-484B-ADB1-A81AB177D732%40gmail.com.
>>
>> -- 
>> 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]
>> <mailto:[email protected]>.
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JH%3DjjhdWOkj8xErUvOPqwn4FYUvCXJcaRe4og4pUQwRA%40mail.gmail.com
>> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JH%3DjjhdWOkj8xErUvOPqwn4FYUvCXJcaRe4og4pUQwRA%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 [email protected]
> <mailto:[email protected]>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/44a7d143-21d0-6450-3e30-d6001a6867c8%40resilia.nl
> <https://groups.google.com/d/msgid/elixir-lang-core/44a7d143-21d0-6450-3e30-d6001a6867c8%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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/c0b39030-ac84-846f-d3c0-9f9a85401fd2%40resilia.nl.

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to