11 ms·
Haskell RecordDotSyntax language extension proposal (Accepted)
- ben509 6y agoIt looks sane and solidly specified. I never trusted DisambiguateRecordFields, so I'll finally ditch a lot of ugly prefixes I stuck in there. The whitespace requirements, though, are going to lead to some confusing error messages for newbies who want to type myRecord . someField
- sanxiyn 6y agoWhitespace requirement is unfortunate but probably necessary. Changing meaning of . from function composition to anything else will break approximately 100% of existing Haskell libraries.
- IncRnd 6y agoHow would this break 100% of existing libraries? Is there a breaking case outside of where an existing composed function and record name are identical? Haskell is a strongly typed language, where that doesn't happen.
- runeks 6y agoI am under the impression that it’s undesirable to have the syntactical meaning change based on the type of the expressions involved.
- tikhonj 6y agoHaskell record fields get turned into top-level functions that act as field accessors. That is, if we have a record: data Student = Student { name :: Text, school :: School } we end up with two functions 'name' and 'school' in scope that extract the values of their respective fields: name :: Student -> Text school :: Student -> School This means it is common for field names to be composed with functions. I write code like this all the time: schools = map (schoolId . school) students This is compounded by the fact that a lot of newtypes use record syntax to define functions in this style that are even named like functions: newtype Reader r a = Reader { runReader :: r -> a } Importing runReader from a module, you might not even know that it's implemented as a record field! This is pretty handy since you can change the implementation of runReader from being a record field to a normal function without breaking most of your callers. (In fact, I think this might have happened with runReader being implemented in terms of runReaderT.) This kind of pattern means that people write lots of code that uses record fields as functions, sometimes without even realizing that they are using a record field! The practical upshot is that myRecord . someField is used with . as function composition all the time, so any proposal that cares about backwards compatibility can't change the meaning of . in that context.
- IncRnd 6y agoI think you misread my post. Identical names doesn't just mean the same type. The change appears to just be syntactic sugar and so would not conflate anything.
- tikhonj 6y agoRight, I think I misunderstood your question. Rereading now, it seems you're asking how interpreting a . b as record access only if b is a record field in scope would break existing code. The new record system is implemented on top of a HasField typeclass under the hood. Thanks to the way Haskell's typeclasses work, this means that the instances that define what fields a record has will be in scope when you import the module containing that record—directly or indirectly—even if you don't import the record itself. This means that even if we do not have a record with a field b in scope, we might still have the instance for some other record in scope—leading to ambiguity in previously unambiguous code. Moreover, even if a . b is unambiguous right now, a record with a field called b might be added somewhere deep in the codebase and cause unexpected ambiguities in the future. I suppose you could say that a . b is composition whenever a function b is in scope, even if there is also a record with a field called b. That rule would not break existing code, but it also seems inconsistent and confusing—a.b and a . b are sometimes the same and sometimes different, depending entirely on whether b is in scope. Making the meaning of . purely lexical rather than depending on what's in scope seems a lot more consistent and easier to follow. It's already the case with Haskell anyway, since . can mean function composition or a qualified name depending on the spaces around it. This isn't an ideal situation, but I don't think we can aim for "ideal" in a living language that's 30 years old.
- ben509 6y agoThe breakage problem is interesting. Right now, a.b parses just fine, so existing code will break when this is turned on. And that is an issue as people often turn extensions on per project, but it's also something a code formatter may be able to fix. (Hopefully britanny will get an update to handle this correctly!) They could, In Theory, rename the composition operator selectively when this language extension is enabled. That'd mean Prelude has to change what operators it exports depending on what extensions are enabled, which would be horribly magical.
- platz 6y agosince basically no other language with dot syntax lets you do myRecord . someField with a space in between, it doesn't seem like something that will actually trip newbies up
- sanxiyn 6y agoHuh? It works in C (I just tried) and I bet in most other languages.
- agency 6y agoWorks in JS as well. Though I have never seen anyone put whitespace around field accesses like that, in any language.
- taeric 6y agoNewlines are pretty common. Especially in chained cases. No?
- platz 6y agoi guess i forgot about the newlines case, and just had never seen anyone put spaces in like that without newlines, so figured it didn't exist. strange that it's even allowed.
- codeflo 6y agoThat’s because C-like languages don’t distinguish newlines from other whitespace. If you want foo() .filter(...) .map(...) then you have to allow foo() .filter(...) .map(...) as well, as a matter of principle.
- eru 6y agoYes. Though even Python, which does treat newlines different from other whitespace, allows `object . someMethod` with spaces on either side.
- bweitzman 6y agoWow this is great! Fixing Haskell's arcane record system would be a huge step towards more mainstream adoption. It was certainly my largest pain point with the language over years of use in production.
- eru 6y agoThat's interesting! I would have expected space-buildups from accidental laziness to be a bigger problem in production?
- whateveracct 6y agoThat is not an especially big production problem. Of course you don't want it to happen. But it rarely happens. And it happens even less if you use -XStrictData or manually make your record fields strict since that's by far the most common cause: lazy records used as accumulators. I would say in my 5 years of production Haskell, I've seen that sort of thing deployed in production _maybe_ once, and I've run into it during development once or twice. One time it was in integration testing, and a simple heap profile made it clear it was in a library dependency. And a quick look at the library source was enough to diagnose it. It was like an hour or two tops from "we have a space leak somewhere" to "here's a PR to a third-party library fixing the leak."
- eru 6y agoThanks! Most of my production experience with 'Haskell' was with Standard Chartered's Mu dialect, which is strict anyway. A bit of a problem when you first start using Haskell seriously is that the IO mechanisms built into the prelude are very slow once you get to even only a few MiB. That's easily remedied by switching to eg ByteStrings, but it's still a bit annoying that what the language presents as the 'default' is such a toy implementation.
- moonchild 6y agoPossibly recommend changing link to the proposal itself[1]? Both pages link to each other, but the discussion is unlikely to be of very much note unless one has already read the proposal being discussed. 1. https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0282-record-dot-syntax.rst https://github.com/ghc-proposals/ghc-proposals/blob/master/p...
- gregwebs 6y agoSimon Peyton Jones: "One possible reaction to a diversity of opinion is to do nothing and wait for clarity to emerge. That is often the right choice, but not (I believe strongly) in this case. We have waited a long time already -- I have been engaged in debate about this topic for over two decades -- and I think it's time to decide something."
- ed25519FUUU 6y agoNot a Haskell programmer so I didn’t know about this issue, but to me this change is night and day better.
- dmkolobov 6y agoIt's a nuanced issue, because Haskell extensively uses the dot (.) infix operator to mean function composition. Currently, the expression `f.g` will attempt to compose the function bound to `f` with the function bound to `g`.
- runeks 6y ago> Currently, the expression `f.g` will attempt to compose the function bound to `f` with the function bound to `g`. So what will this mean now? Will function composition require a space between the dot, or how will a “record dot” be disambiguated?
- duijf 6y agoFrom https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0282-record-dot-syntax.rst#lexer https://github.com/ghc-proposals/ghc-proposals/blob/master/p... .g is a field selector. f.g is field selection of a record. f . g is function composition. f. g is function composition.
- moomin 6y agoWhilst I'm not going to argue the proposal is a bad thing, I think you've just summarized why some are giving it the side eye pretty well.
- hopia 6y agoNothing is better than seeing the broken pieces of the foundation fixed in Haskell. Records were my number one gripe in the language that otherwise does so many advanced things so uniquely well.
- eru 6y agoI also liked that they managed to pull off https://wiki.haskell.org/Foldable_Traversable_In_Prelude https://wiki.haskell.org/Foldable_Traversable_In_Prelude
- tartrate 6y agoHow did Idris solve this again btw?
- mixedCase 6y agoIdris 2 added dot notation. Idris 1 simply disambiguates based on type or namespaces each accesor.
- imdoor 6y agoDoes the proposal have any effect on lens usage? EDIT: I mean, lenses and RecordDotSyntax both provide a way to access nested fields in records, so the new syntax would take away some of the reasons to use lenses. But are there any other relations among the two, or do lenses and RecordDotSyntax just provide two different solutions to the same problem?
- kqr 6y agoLenses (or, rather, optics) are still far more powerful than plain record field getters. Optics allow - Mutation of nested records, - Iteration of collections, - Folding of collections, - Viewing discriminated cases that may or may not exist, - Building a structure around a value, and so on and so forth.
- kreetx 6y agoBut this has nested mutation, no?
- imdoor 6y agoYeah, there are still a lot of reasons to use lenses. But to rephrase - i was wondering if lenses and the proposal somehow "interact". Actually, I just found some of what i was after in this comment [1]. [1] https://github.com/ghc-proposals/ghc-proposals/pull/282#issuecomment-546159561 https://github.com/ghc-proposals/ghc-proposals/pull/282#issu...
- jakuboboza 6y agoImho this looks good. It will save on key strokes and maybe bring more readability. I always liked "." or "->" syntax in different languages. My only worry is . being function composition operator so it might not be crazy amazing for readability only you get a . b . c . d.e So maybe it should have been something like "->".
- contravariant 6y agoI thought conflating the dot with function composition wouldn't be too bad because they're similar in a sense, but then I remembered that the two work in the reverse order in the sense that: (.a.b.c) . (.d.e.f) = (.d.e.f.a.b.c) If you use -> both for the member function and for reverse function composition you'd get: (->a->b->c) -> (->d->e->f) = (->a->b->c->d->e->f) which is somehow nicer, especially because accessing a member and composing functions become the same thing when you view a value as the unique function sending the initial object to that value. But unfortunately we've spent centuries composing functions the wrong way round, so now we're forced to deal with the consequences.
- srtjstjsj 6y agoAlmost, but you are conflating function composition (lambda x: lambda y: f(x(y))) with function application (lambda x: f(x)).
- sudomakeup 6y agoYou can do left to right function composition with (>>>) Given functions f, g , h, h >>> g >>> f is equivalent to f . g . h Its from Control.Category, so it generalizes to other things, but for functions specifically its left to right composition. ( theres also (<<<), which in this case is identical to (.) ) ( Also present in Control.Arrow . Why? I dont know this topic well enough to explain. My understanding is limited to what these operators mean in the specific of functions. Functions(->) are a type as well, so they can be instances of typeclasses. for example instance Functor ((->) r) where fmap = (.) )
- runeks 6y ago> So maybe it should have been something like "->". The problem is that "->" has an even more special meaning than "." in Haskell, since "->" separates the arguments of an unnamed function from its definition. So, for example addOne = \x -> x + 1 and addOne x = x + 1 are equivalent. This means that "->" is about as special as "=". Which leads me to think it would require some very substantial changes to the parser, and perhaps even defeat the purpose of increased readability. For example, how readable is the following? getY = \x -> x -> y (which reads as "getY = \x -> x.y" in the current proposal)
- hardwaresofton 6y agoFor those that want to see what it actually looks like: https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0282-record-dot-syntax.rst#examples https://github.com/ghc-proposals/ghc-proposals/blob/master/p... data Grade = A | B | C | D | E | F data Quarter = Fall | Winter | Spring data Status = Passed | Failed | Incomplete | Withdrawn data Taken = Taken { year :: Int , term :: Quarter } data Class = Class { hours :: Int , units :: Int , grade :: Grade , result :: Status , taken :: Taken } getResult :: Class -> Status getResult c = c.result -- get setResult :: Class -> Status -> Class setResult c r = c{result = r} -- update setYearTaken :: Class -> Int -> Class setYearTaken c y = c{taken.year = y} -- nested update getResults :: [Class] -> [Status] getResults = map (.result) -- selector getTerms :: [Class] -> [Quarter] getTerms = map (.taken.term) -- nested selector
- misja111 6y agoI love this syntax, it's elegant and a big improvement! When/ how can I use this? Do I have to wait for a new GHC release?
- hardwaresofton 6y agoIt's not released yet AFAIK -- there's a tracking issue on the GHC tracker[0]. I agree that it's very nice, and a big improvement! [0]: https://gitlab.haskell.org/ghc/ghc/-/issues/18599 https://gitlab.haskell.org/ghc/ghc/-/issues/18599
- Lambdanaut 6y agoUgh fucking finally. Too many hours wasted learning the complexities and template macro nonsense of lenses. I am so elated to see this change, even if it is 15 years overdo. Nested selectors in particular are beautiful. Maybe it's time again to do me some haskell for great good.
- prof-dr-ir 6y ago
- TazeTSchnitzel 6y agoPureScript, which borrows heavily from Haskell's syntax (you could say it is Haskell for JavaScript but with strict evaluation) already had something like this for syntax for accessing records (JS objects). It wisely didn't also use the dot for function composition however. I hope it will be the norm to use some alias other than (.) for function composition when this extension is used.
- sullyj3 6y agoI'm a big fan of the flow package (https://hackage.haskell.org/package/flow https://hackage.haskell.org/package/flow), which defines (<.) :: (b -> c) -> (a -> b) -> a -> c and (.>) :: (a -> b) -> (b -> c) -> a -> c
- srtjstjsj 6y agoHaskell is full of things (hello, most of Prelude) where the default thing is terrible and the correct thing is in a package (usually a choice between multiple incompatible packages). It makes it extremely uncomfortable to grow from newbie to intermediate.
- singularity2001 6y agoWhere can one track the progress of the implementation and be informed when it's ready?
- cptwunderlich 6y agoHere the ticket: https://gitlab.haskell.org/ghc/ghc/-/issues/18599 https://gitlab.haskell.org/ghc/ghc/-/issues/18599
- singularity2001 6y agoThanks ! I can't find a subscribe button in gutlab but I will set a reminder
- pbiggar 6y agoThe syntax to update records doesn't seem particularly nice: e{lbl = val} Though this has been a part of haskell for a while it seems: > Note: e{lbl = val} is the syntax of a standard H98 record update FSharp, OCaml and Elm have not got great solution here either: { e with lbl = value } // ocaml/fsharp { e | lbl = value } Do any functional languages have anything as nice as: e.lbl = value
- frou_dh 6y agoTo me those ML syntax look very in keeping with the fact that it's an immutable update. Don't mind them at all.
- tome 6y agoAs frou_dh hints, the updates are not done in place, so it would have to be something like new_e = e.lbl = value at which point "nice" is not how I would describe it.
- garethrowlands 6y agoLens has this: e & lbl .~ value It's not an in-place update like `e.lbl = value`.
- srtjstjsj 6y agoThat doesn't solve GP's problem of "not looking exactly like C++/Java/Python"
- pbiggar 6y agoMy goal isn't to look _exactly_ like C++/etc, but rather to have a fairly natural duality between read and write. You can read with: a.b.c.d but a write is: let a = {a with b = { a.b with c = { a.b.c with d = 5 } } } } which is much much more verbose than the OO/imperative: a.b.c.d = 5
- kqr 6y ago
- srtjstjsj 6y agoWhat's sad is that only reason this situation is so controversial is because all of programming language design is constrained by the 19th Century typewriter keyboard.
- thomasahle 6y agoModern mobile keyboards have much fewer symbols still.
- johndoe42377 6y agoAs record slot's value retrieval (I love old lisp terminology) could be viewed as a pure function (given the record as a parameter) it is reasonable to think of .slot as a function. The old classic languages just create so-called selectors in the global namespace, like defstruct macro does. This is not the best possible solution. However, the . is used for function composition, so the better alternative could be the -> syntax, reflecting that in Haskell everything is a pointer.
- whateveracct 6y agoEverything in Haskell records is _not_ necessarily a pointer though thanks to {-# UNPACK #-}
- AzzieElbab 6y ago... and if stick a lambda in there the whole thing becomes on object :)