In the process of upgrading an Elm app to 0.19, I’ve hit the following snag. I have a module that looks something like this:
module Widget exposing (Widget, index, toString)
type alias Widget = { index: Int, name: String }
index : Widget -> Int
index = .index
toString : Widget -> String
toString { index, name } = "Widget " ++ name ++ " number " ++ String.fromInt index
The issue is that the record destructuring in the last line rebinds the name index, and so is disallowed by the compiler in 0.19. The choices to avoid this are:
rename the accessor function index
rename the fields of the record
don’t use a record type but rather type Widget = Widget String Int
don’t use record destructuring, but rather:
toString widget =
let
nam = widget.name
idx = widget.index
in
"Widget " ++ nam ++ " number " ++ String.fromInt idx
However, the original code feels to me like something that ought to work. I can’t decide if this is because
rebinding top-level module names ought to be allowed (but not rebinding names in nested scopes within a single toplevel definition, which I agree it is sensible to disallow)
there should be some kind of exception to the no-rebinding rule for record destructuring, since the names used there must correspond to the names in the record and cannot be freely chosen by the programmer (imagine the case where the Widget type was not under my control but was imported from some other library I used)
my intuitions are mistaken (always a possibility); the structure of the code sample above needs to be changed in one of the ways I listed (or one that I did not think of)
In any event, I thought I would make a post to see what others thought about the situation.
That’s indeed a better alternative to the code I wrote under solution 4. Thanks for spotting it! I think the general question still remains though: is this a situation in which record destructuring should be avoided?
I think the consistent solution would be to remove record destructuring from the language. I’ve yet to run across a situation where referencing record fields explicitly as in @hpate’s reply isn’t clearer.
Please, no. Pattern matching for function and let args is something I use every day. I would much rather return to the norm in languages with lexically bound identifiers, shadowing being normal and expected. I even like Elixir’s rebinding of the same variable more than once in a single let. I’d rather say model 10 times, knowing that each one intentionally shadows the one in its scope, than model2, model3, etc., and then get confused which one to reference and miss some state.
My (albeit not extensive) understanding is that in a declarative functional language like elm, it is better practice to avoid naming with (procedural) verbs lite get and to. I think there is a genuine blurring of the lines between what records might hold and functions that access them which does making non-conflicting naming more challenging.
Another observation is that if you expose the Widget alias, it is obvious to any consumer of it that .index will extract that field, so does it really need a top-level accessor function?
Well, in the underlying code index is a lens (from the monocle library), so it’s not exactly equivalent to an accessor function.
But more generally a getter function like Widget.index is for use by other modules when accessing a widget’s index. In that way, if the representation of widget indexes changes in the future, code that manipulates widgets and their indices won’t break; I just have to make the corresponding change to Widget.index. Using .index record accessors would mean that a change in representation would need to be matched by changes everywhere the code does myWidget.index (across many modules).
It’s not just destructuring though, its documenting which fields of Widget are actually required by the toString function. Should Widget later grow to be more complicated, we can see at a glance that toString only actually uses the index and name fields. I think being able to reason like this about complicated code when trying to track down problems is a huge gain, and would be very sorry to lose the ability to pattern match like this.
(Of course I’m not saying any of that is required in a simple case like that.)