I am not sure it should be a property of the parent function. We *could*
have both operations of traversal mixed:
get_in(data, [:key, Access.at!(0), :other_key, Access.at(0),
:final_key])
This would provide granular control over which paths are strict, which ones
are not, which is more flexible than saying it is all strict or all lax.
The problem is that we already shipped "Access.at(0)" but it behaves like
"Access.at!(0)" in the approach propose above. So we need to find out a
different naming schema or a composable API, which is the
Access.unless_nil, I originally proposed. The idea of having
"Access.nillable([path])" was just a shortcut for making it easier on the
eyes, in the same way :key today is equivalent to something like
"Access.key_or_nil(:key)".
On Sun, Feb 9, 2020 at 1:37 PM Wiebe-Marten Wijnja <[email protected]> wrote:
> 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]> wrote:
>
>> On Feb 8, 2020, at 6:34 PM, José Valim <[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].
>> 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].
> 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].
> 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
> <https://groups.google.com/d/msgid/elixir-lang-core/c0b39030-ac84-846f-d3c0-9f9a85401fd2%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/CAGnRm4KvkPR9zXY1fueuANjWvWE%2B6Nbqmu8imp__uBYiwnjybg%40mail.gmail.com.