To atom or not to atom - that is the question :)

It’s a good nitpick! I’ve been learning Lua recently and that is on my mind. I also don’t like what I said about JS because I was also thinking of Lua :sweat_smile: Close enough, though. Honestly, I was pretty beat and a little bit tipsy when I wrote all that which apparently makes me think every language is Lua.

It’s not a beef :stuck_out_tongue: … it’s more like understanding that the comunity has a bias torwards it that I don’t like. And it’s a thing with “community” in general because the language doesn’t force you to use one or the other, but the community in itself that chooses to express with the language their biases.

I’m quoting all of you together because my line of thought gonna address some points of all those commentaries.
I’m saying it’s an “OO habit” not because of the syntax but is exactly about semantics @msimonborg, and that is showed with the points that @sodapopcan and @zachallaun made.
Using dot notation isn’t about the key being defined, isn’t about value, but just about assumption. And the assumption is that the data has a particular shape, there is nothing in elixir that grants a key gonna be present in a map. OO and Haskell for example have tools to ensure that, and most of the time it’s not an assumption but a certainty that the data has that particular shape, because you certified that with an object or a type. But in Elixir it’s always an assumption.
So when you use dot notation and assume that the key is always there, this assumption might be wrong. The only tool that the language provides to have that kind of certainty is pattern match. And it goes back to my previous point, dot notation is a bad way to communicate that you need the data to have a particular shape, either internally or externally.

I‘m not sure how there‘s a difference between pattern matching and dot notation for maps. The error cases are hardly different. It‘s an exception when things don‘t align with the expectations. It‘s only a different type of exception.

1 Like

It’s not an assumption, it’s an expectation.

Sure, but you can. Just because a language is dynamic doesn’t mean that it can’t have contracts. What the language isn’t stopping you from doing is using a bare map with a well-defined shape. In those cases you should use . (or fetch) which is signal that it’s expected to be there and there is a bug if it’s not.

…and I assume we aren’t counting structs as maps here (although I would).

And I’m now confused about the OO comparison. I would use some_hash.fetch(:key) all the time in Ruby which is the exact same functionality as . in Elixir.

Edit as that was probably confusing: I just mean that I would use both fetch and [] in Ruby depending on whether I expected the data to be there or not. I’m just trying to say that having a scenario where you expect a key to be there is not OO-specific.

:point_down:

I’m counting it because there is nothing special on structs that would change the things that I said here.

and that goes back to the thing that I said before. the dot notation is a worse way to define a contract than the pattern match in the function definition, because I need to go and read the whole function body to understand what keys are required there.

My point there is more of the habit not that foo.bar is OO-ish, the habit of dealing with data as a rigid shape. The idea that that thing is always there, binding your reasoning to that necessity.
It kinda remember me about this excerpt from a Rich Hickey talk:

This is such a weird hill to die on, but onward and upward!

I’m not sure we disagree about that much here beyond a choice of words that @sodapipcan and I take some issue with: that dot notation is some kind of OO habit/assumption that should largely be avoided. You’ve expanded on that a bit now, which I appreciate, to state that the issue is more with expecting some kind of rigid structure in your data.

Let’s consider a few situations before addressing the last point you made and the link to Rich’s talk.

  1. You’re dealing with user-submitted data or options. Obviously, we don’t use dot notation here because the keys may not be defined.

  2. You’re dealing with required input from a user of your library. You shouldn’t use dot notation at the boundary, but should instead validate using pattern matching or other and provide useful errors.

  3. You’re working in the private core of your application, dealing with data that you’ve defined and validated. It’s absolutely fine to use dot notation here! You can also use pattern matching if it’s more convenient, but it’s not always — sometimes it’s overly verbose especially if accessing many attributes of something. At this point, the key not being defined is a bug in the code and I want it to throw, either due to dot notation or a function clause match error if I’m pattern matching in the function head.

So, should you always prefer dot notation? No, but the semantics of it are absolutely useful and correct in many circumstances.

As for Rich’s talk: I’m not sure he’s saying what you think he’s saying. Rich advocates for genetic data structures over classes with getters because it allows the use of a common language to deal with them. We don’t run afoul of that with dot notation — structs are just maps! In fact, you could argue that dot notation is more in line with what Rich would want than, say,

defp my_internal_fn(%Data{foo: foo}), do: …

because the above is requiring that an element of Data be passed in instead of any map that meets the contract you defined (that foo exist on the structure).

I’m not suggesting that the . is the contract. . is a signal at the callsite that what is being dealt with is a map whose key is always expected to exist because the code creates it ensures it’s always there. The dot in this case is great for scannability.

I think what’s going on here, and correct me if I’m wrong, but you are advocating for never designing with hardened maps, ever (I’m familiar with the Rich talk). That’s all well and good if that’s the case, it just wasn’t clear to me.

imo, pattern matching is more explicit, for the reasons I explained. I don’t need to read anything else then the function definition to understand that you need that field to work.

I agree, but a lot of the usage that you have of structs in the wild are just half-baked objects. it’s easy to spot, usually you have a new function in the module that defines the struct. :man_facepalming:

i’d say it’s actually worse for scannability because it has conflicting usages while something[field] is just used for accessing key-value data structures…
just for example:

var1 = %{upcase: &String.upcase/1}
var2 =  %{upcase: %{value: "UPCASE"}}
var3 = String

#using dot notation
var1.upcase.("something")
var2.upcase.value
var3.upcase("something")

#using access
var1[:upcase].("something")
var2[:upcase][:value]
var3.upcase("something")

That’s not what I mean by scannability but at this point we’re just talking past each other.

if you provide examples of what you think is a scenario where dot notation makes identifying something in the code more easy, I can understand. Because from what I understood from the initial post that you sent and the video that explains about scanning code, that’s what I’ve got. A scannable code is one that is easier to spot and put a part different code constructs so you find exactly what you’re looking for more easily, so the more conflicting a syntax is, the worse it is for scannability.
At least it’s what I’ve got from this video.

I’m happy to move this to DMs as I’m actually somewhat interested in continuing (even if we’re somewhat bike shedding) but I don’t want to dominate this thread with our back-and-forth.