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.
signature.asc
Description: OpenPGP digital signature
