I'm uncertain how best to proceed at this point. I have some code that is a 
proof of concept of what I believe is a compromise. I don't wish to open 
another PR prematurely and have it closed, so I'll try one more round of 
discussion here.

I have added 2 new Access functions: path/1 and path!/1. The first one is nil 
safe with a wrapper as José mentioned earlier. The second one offers consistent 
assertiveness, not the hybrid behavior of get_in today. Here's the doctests I 
started with for further discussion.

for path/1
      iex> get_in(%{}, Access.path([:a, :b]))
      nil

      iex> get_in(%{}, Access.path([:a, Access.at(0)]))
      nil

      iex> get_in(%{a: nil}, Access.path([:a, Access.at(0)]))
      nil

      iex> get_in(%{a: []}, Access.path([:a, Access.at(0)]))
      nil

for path!/1
      iex> get_in(%{}, Access.path!([:a, :b]))
      ** (KeyError) key :a not found in: %{}

      iex> get_in(%{}, Access.path!([:a, Access.at(0)]))
      ** (KeyError) key :a not found in: %{}

      iex> get_in(%{a: nil}, Access.path!([:a, Access.at(0)]))
      ** (ArgumentError) Access.path!/1 encountered nil

    This one is unimplemented yet, but it should raise for consistency:

      iex> get_in(%{a: []}, Access.path!([:a, Access.at(0)]))
      ** (ArgumentError) [] has no element at index 0

For comparison, this is how get_in behaves with these cases today, highlighting 
the inconsistency that is the core of what bugs me. Half the cases return nil 
and half raise.
    iex> get_in(%{}, [:a, :b])
    nil

    iex> get_in(%{}, [:a, Access.at(0)])
    ** (RuntimeError) Access.at/1 expected a list, got: nil

    iex> get_in(%{a: nil}, [:a, Access.at(0)])
    ** (RuntimeError) Access.at/1 expected a list, got: nil

    iex> get_in(%{a: []}, [:a, Access.at(0)])
    nil

If this is a welcome direction, I'll be happy to submit my PR and work through 
details of optimization, cleanup, exception wording, etc.

-Greg Vaughn

> On Feb 8, 2020, at 1:47 AM, José Valim <jose.va...@dashbit.co> wrote:
> 
> For now, I don't think we should add a new function to Kernel. So we should 
> find something that makes a path nillable for definition in Access, and then 
> you can define get_path in your app if that's what you prefer.
> 
> On Sat, Feb 8, 2020 at 2:23 AM Greg Vaughn <gvau...@gmail.com> wrote:
> I recognize more use cases than mine. Given we will not change Kernel.get_in, 
> I have ideas for other, less "nillable" names, such as "get_path" or 
> "path_in" to make mil-safety less of an exceptional situation. Path 
> expressions, as originally used in object oriented databases, typically did 
> not raise exceptions when some data did not match expectations. This specific 
> naming discussion can be deferred though.
> 
> I am in agreement on writing assertive code. That is the very reason I want 
> something in the standard library that is a nil-safe navigation through 
> untrusted input. I don't want to write an `if` or `with` dealing with each 
> list key that might be nil, when I don't have to do it for maps. It it is the 
> very reason I view the dot syntax as very confident keys exist vs. a get_in 
> call which uses Access to determine existence of keys/lists.
> 
> -Greg Vaughn
> 
> > On Feb 7, 2020, at 6:55 PM, José Valim <jose.va...@dashbit.co> wrote:
> > 
> > > What I find curious is that once we implement Kernel.nillable_get_in, why 
> > > would anyone choose to use Kernel.get_in instead? 
> > 
> > When I don't expect anything to be nil, I want it to fail as soon as 
> > possible, instead of having nil further creeping into the system. 
> > Personally, most of the times I used get_in and friends, I am working with 
> > structured data (the opposite of your use case). If any nil shows up, it 
> > should be an error.
> > 
> > And changing get_in may not break code, expectations I had when I wrote the 
> > code would certainly be broken. And I would personally be unhappy if we 
> > simply changed get_in without introducing an option to write assertive 
> > code. Writing assertive code is an important of Elixir. It is why we have 
> > map.foo in addition to map[:foo]. So I think it is best to remove changing 
> > get_in from the discussion altogether, I don't see it happening.
> > 
> > We can continue discussing alternatives though.
> > 
> > 
> > 
> > On Sat, Feb 8, 2020 at 1:47 AM Greg Vaughn <gvau...@gmail.com> wrote:
> > One more point. Even if my proposal is not accepted, these docs for 
> > Kernel.get_in really need to change
> > 
> > In case any of the entries in the middle returns nil, nil will be returned 
> > as
> > per the Access module:
> > 
> >     iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
> >     iex> get_in(users, ["unknown", :age])
> >     nil
> > 
> > The Access module guarantees no nil-safety. It's an "accident" that 
> > Access.get does.
> > 
> > -Greg Vaughn
> > 
> > 
> > > On Feb 7, 2020, at 4:40 PM, José Valim <jose.va...@dashbit.co> wrote:
> > > 
> > > Hi Greg, I have been thinking more about this too, and I think there are 
> > > some neat ways we can make this more accessible:
> > > 
> > > We could introduce Access.nillable (please suggest a better name) that 
> > > you would use like this:
> > > 
> > >     get_in(root, Access.nillable([:foo, :bar, Access.at(0)]))
> > > 
> > > Basically, it traverses the path and sets all functions in the path to 
> > > something that handles nil. In your apps, you can quickly encapsulate it 
> > > like this:
> > > 
> > >     nillable_get_in(root, [:foo, :bar, Access.at(0)])
> > > 
> > > It is concise, backwards compatible, and clear in intent.
> > > 
> > > Thoughts?
> > > 
> > > On Fri, Feb 7, 2020 at 11:35 PM Greg Vaughn <gvau...@gmail.com> wrote:
> > > I just wanted to follow up and summarize here. I submitted a PR 
> > > https://github.com/elixir-lang/elixir/pull/9773 with some more 
> > > discussion, but the core point there was that we needed more discussion 
> > > on the core list before a PR and it was closed. Nil-safety by default is 
> > > undesirable in more Access functions than Access.get.
> > > 
> > > I'm exploring this on my own in my own codebase as I rework all the 
> > > get_in calls I assumed were nil safe despite using Access.at. I am quite 
> > > against a solution that is more verbose to gain mil safety as I use this 
> > > at the edges of my system in an anti-corruption-layer. I'd rather see 
> > > this implemented once, well, in the standard library than expect 
> > > thousands of projects to do it themselves or bring in a 3rd party 
> > > solutions to achieve it.
> > > 
> > > Feel free to discuss some more.
> > > 
> > > -Greg
> > > 
> > > > On Jan 30, 2020, at 12:02 PM, Allen Madsen <allen.c.mad...@gmail.com> 
> > > > wrote:
> > > > 
> > > > I'm in favor of them being nilsafe by default.
> > > > 
> > > > Allen Madsen
> > > > http://www.allenmadsen.com
> > > > 
> > > > 
> > > > On Wed, Jan 29, 2020 at 11:24 AM Tor Bjornrud <bjorn...@gmail.com> 
> > > > wrote:
> > > > I wouldn't mind having opts for something like this.  Avoids creating a 
> > > > slew of Access functions that then become difficult to sift through.
> > > > 
> > > > %{"items" => nil} |> get_in(["items", Access.at(0, nilsafe: true)
> > > > 
> > > > On Tuesday, January 28, 2020 at 8:10:04 PM UTC-6, Greg Vaughn wrote:
> > > > Thanks, José. I agree with the need to be consistent. I will look at 
> > > > the bigger picture, though, like Manfred I find the addition of "maybe" 
> > > > to be awkward, so my preference is to have the existing recommended 
> > > > functions in the Access module intended for use with get_in to be 
> > > > consistently nil safe. I'm open to more ideas, too. 
> > > > 
> > > > -Greg Vaughn 
> > > > 
> > > > > On Jan 28, 2020, at 12:45 PM, José Valim <jose...@dashbit.co> wrote: 
> > > > > 
> > > > > The proposal is reasonable however it would introduce an 
> > > > > inconsistency since the other selectors in Access, such as 
> > > > > Access.key, are also not nil safe. So whatever solution we choose 
> > > > > needs to be consistent. 
> > > > > 
> > > > > One possible suggestion is to introduce a "Access.maybe" that 
> > > > > composes but composition would have to be back to front: 
> > > > > 
> > > > > %{"items" => nil} |> get_in(["items", Access.at(0) |> Access.maybe]) 
> > > > > 
> > > > > Another idea is to introduce maybe_at, maybe_key, maybe_key! and so 
> > > > > on. But I am not sure if this is desirable. Thoughts? 
> > > > > 
> > > > > On Tue, Jan 28, 2020 at 7:33 PM Greg Vaughn <gva...@gmail.com> wrote: 
> > > > > I propose that the function returned from Access.at/1 special case 
> > > > > nil such that the overall Kernel.get_in/2 call returns nil instead of 
> > > > > raising an error. 
> > > > > 
> > > > > Rationale: 
> > > > > I originally blamed this on Kernel.get_in/2 and I'd like to thank 
> > > > > Eric Meadows-Jönsson for explaining the underlying reason to me on 
> > > > > Slack. 
> > > > > 
> > > > > I like to think of Kernel.get_in/2 as a nil-safe way of plucking 
> > > > > values out of nested data structures, but I learned today that is 
> > > > > only partially correct. The nil-safety comes from the underlying 
> > > > > Access.get/2 calls. The docs for get_in includes: 
> > > > > 
> > > > >  In case any of the entries in the middle returns nil, nil will be 
> > > > > returned as per the Access module: 
> > > > >     iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}} 
> > > > >     iex> get_in(users, ["unknown", :age]) 
> > > > >     nil 
> > > > > 
> > > > > and I expected use of Access.at/1 in my keys to act similarly, but it 
> > > > > doesn't. For example: 
> > > > > 
> > > > > iex(185)> %{"items" => ["desired_value"]} |> get_in(["items", 
> > > > > Access.at(0)]) 
> > > > > "desired_value" 
> > > > > iex(186)> %{"items" => nil} |> get_in(["items", Access.at(0)]) 
> > > > > ** (RuntimeError) Access.at/1 expected a list, got: nil 
> > > > >     (elixir) lib/access.ex:663: Access.at/4 
> > > > > 
> > > > > I propose that the function returned from Access.at/1 special case 
> > > > > nil such that the overall get_in/2 call returns nil instead of 
> > > > > raising an error. I have not dug into the source yet but I'm happy to 
> > > > > work up a PR if there is interest in this change. 
> > > > > 
> > > > > -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 elixir-l...@googlegroups.com. 
> > > > > To view this discussion on the web visit 
> > > > > https://groups.google.com/d/msgid/elixir-lang-core/6B6AB775-F3D5-40E5-BFBD-9852FBCBD1D0%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 elixir-l...@googlegroups.com. 
> > > > > To view this discussion on the web visit 
> > > > > https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4KZPZ5mpP6SSzhmq3jpuZBYA1irpmOa19UNH2fS_3QKQA%40mail.gmail.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 elixir-lang-core+unsubscr...@googlegroups.com.
> > > > To view this discussion on the web visit 
> > > > https://groups.google.com/d/msgid/elixir-lang-core/1ae0b9d3-9471-4750-8734-281033e9a1dc%40googlegroups.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 elixir-lang-core+unsubscr...@googlegroups.com.
> > > > To view this discussion on the web visit 
> > > > https://groups.google.com/d/msgid/elixir-lang-core/CAK-y3Cu%2BGBO1RWsdAjAHoaukV3w4QJPPdqqNU_miQ_%3Dv5%3DdDeQ%40mail.gmail.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 elixir-lang-core+unsubscr...@googlegroups.com.
> > > To view this discussion on the web visit 
> > > https://groups.google.com/d/msgid/elixir-lang-core/22988265-AB94-4666-894B-9ECF7B87905D%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 elixir-lang-core+unsubscr...@googlegroups.com.
> > > To view this discussion on the web visit 
> > > https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4%2B5ovo9YdQHQO2m6i%3DL_SxPKRN4O4fZejH%3DXMXfJWwWkQ%40mail.gmail.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 elixir-lang-core+unsubscr...@googlegroups.com.
> > To view this discussion on the web visit 
> > https://groups.google.com/d/msgid/elixir-lang-core/4CE0D4F8-A341-4832-AC94-BDBC0D7E0911%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 elixir-lang-core+unsubscr...@googlegroups.com.
> > To view this discussion on the web visit 
> > https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4K-yXsZ2mxJ3sg6knRwLAFmMUky6c0G50gaBVnDpb18fA%40mail.gmail.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 elixir-lang-core+unsubscr...@googlegroups.com.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/elixir-lang-core/E9C59B12-1663-45E9-A8C3-163F75895D52%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 elixir-lang-core+unsubscr...@googlegroups.com.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4KwMg%3DHDM-B4B2Qh1irafG8_Y8tW%2BLYRTKGDoy%3DHOtkNA%40mail.gmail.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 elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/D19053D7-E80B-4844-856E-2B63E6C71AE5%40gmail.com.

Reply via email to