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].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/44a7d143-21d0-6450-3e30-d6001a6867c8%40resilia.nl.

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to