10

While reading library code here I have noticed a really weird looking syntax that I can't make sense of:

momenta
    :: (KnownNat m, KnownNat n)
    => System m n
    -> Config n
    -> R n
momenta Sys{..} Cfg{..} = tr j #> diag _sysInertia #> j #> cfgVelocities
    --  ^^^^^^^^^^^^^^^ the syntax in question
  where
    j = _sysJacobian cfgPositions

The relevant definitions of System includes a record { _sysJacobian :: R n -> L m n }, and { cfgVelocities :: R n } is part of the record declaration of Config so I believe I know what the code does, I think the code is quite readable, props to the author.

The question is: what is this syntax called and how exactly can I use it?

WorldSEnder
  • 4,875
  • 2
  • 28
  • 64
  • if someone can come up with a better title, feel free to edit – WorldSEnder May 10 '18 at 08:39
  • Is this the `RecordWildcards` extension as seen over at https://stackoverflow.com/a/38052886/14955 ? – Thilo May 10 '18 at 08:48
  • 2
    @Thilo indeed it is. It's a bit bad luck that the syntax is hard to find with search engines with all the non-acii characters, seeing that such a well-received answer already exists. – WorldSEnder May 10 '18 at 08:57
  • `RecordWildCards` is a rather problematic extension for the same reason as unqualified wildcard imports—it makes the code easier to write but harder to read later, since you can’t tell where an identifier came from; plus, a record/module may add a field/export with a name that clashes with other identifiers in scope where you use the record/module. A better solution imo is `NamedFieldPuns`, where you can write `Record { foo, bar, baz }` as an abbreviation of `Record { foo = foo, bar = bar, baz = baz }` (in patterns or expressions)—still fairly succinct, but much clearer & more explicit. – Jon Purdy May 11 '18 at 00:11

2 Answers2

17

In short: it is an extension of GHC called RecordWildCards.

In Haskell you can use record syntax to define data types. For example:

data Foo = Bar { foo :: Int, bar :: String } | Qux { foo :: Int, qux :: Int }

We can then pattern match on the data constructor, and match zero or more parameters, for example:

someFunction :: Int -> Foo -> Foo
someFunction dd (Bar {foo=x}) = dd + x
someFunction dd (Qux {foo=x, qux=y}) = dd + x + y

But it can happen that we need access to a large amount (or even all) parameters. Like for example:

someOtherFunction :: Foo -> Int
someOtherFunction (Bar {foo=foo, bar=bar}) = foo
someOtherFunction (Qux {foo=foo, qux=qux}) = foo + qux

In case the number of parameters is rather large, then this becomes cumbersome. There is an extension RecordWildCards:

{-# LANGUAGE RecordWildCards #-}

this will implicitly write for every parameter foo, foo=foo if you write {..} when we do record pattern matching.

So we can then write:

someOtherFunction :: Foo -> Int
someOtherFunction (Bar {..}) = foo
someOtherFunction (Qux {..}) = foo + qux

So here the compiler implicitly pattern matched all parameters with a variable with the same name, such that we can access those parameters without explicit pattern matching, nor by using getters.

The advantage is thus that we save a lot on large code chunks that have to be written manually. A downside is however the fact that the parameters are no longer explicitly and hence the code is harder to understand. We see the use of parameters for which there exist actually getter counterparts, and thus it can introduce some confusion.

Like @leftroundabout says, probably lenses can do the trick as well, and it will prevent introducing variables that basically shadow getters, etc.

You can also merge the RecordWildCards with pattern matching on parameters, for example:

someOtherFunction :: Foo -> Int
someOtherFunction (Bar {bar=[], ..}) = foo
someOtherFunction (Bar {..}) = foo + 42
someOtherFunction (Qux {..}) = foo + qux

So here in case the bar parameter of a Foo instance with a Bar data constructor is the empty string, we return the foo value, otherwise we add 42 to it.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    I think it's worth mentioning that RecordWildcards is a more ad-hoc and arguably inferior way of adressing the problems that's very well solved by lenses. – leftaroundabout May 10 '18 at 09:20
  • in the OP code `j = _sysJacobian cfgPositions`, what would happen if `cfgPositions` (which isn't shown) were of type `System m n`? would it refer to `cfgPositions` or the first argument to `momenta`, or it would be an error then? ... erhm, I guess it's shadowed, so does come from the 1st argument, and `cfgPositions :: R n`? indeed pretty confusing stuff. – Will Ness May 10 '18 at 12:32
  • @WillNess: since the variable shadow the getters, it will raise an error, since at that point `_sysJacobian` is not the getter, it is shadowed by the attribute of the `Sys` object. – Willem Van Onsem May 10 '18 at 12:35
  • you mean, it *would* raise error if `cfgPositions :: System m n`. So it probably *is* `cfgPositions :: R n`. OK, thanks. – Will Ness May 10 '18 at 12:38
  • 1
    @leftaroundabout I don't agree (or at least don't understand the criticism). I find RecordWildcards are far superior to lenses for the cases where you could choose either. – jberryman May 10 '18 at 20:06
  • @jberryman what I don't like about `RecordWildCards` is that `foo` suddenly means something different depending on pattern matches made in an outer scope – it switches its type from `Bar -> Int` to just `Int`. This adds to the confusing double-role that record fields have already. Code like `grompf (Bar{..}) = someOtherBar {foo=foo}` is really weird. Of course the ambiguity between different meanings is to some degree desired because you do want to do different things here, but with `RecordWildCards` it's very ad-hoc on the syntax level. With lenses, it's nicely employing the type system. – leftaroundabout May 10 '18 at 20:24
  • @leftaroundabout I would actually be quite happy if `RecordWildCards` somehow disabled both `foo` as `:: Bar -> Int` and record update syntax, but yeah I understand your concern – jberryman May 10 '18 at 21:14
4

It's the RecordWildCards syntax extension. From the docs:

For records with many fields, it can be tiresome to write out each field individually in a record pattern ... Record wildcard syntax permits a ".." in a record pattern, where each elided field f is replaced by the pattern f = f ... The expansion is purely syntactic, so the record wildcard expression refers to the nearest enclosing variables that are spelled the same as the omitted field names.

Basically it brings the fields of a record into scope.

It is particularly useful when writing encoders/decoders (e.g. Aeson), but should be used sparingly in the interest of code clarity.

Jordan Mackie
  • 1,193
  • 10
  • 17
  • 2
    I would add that `RecordWildCards` is more readable if it is used in the same package that the type is defined, but once you use it in another package, it can be very unclear where those functions/record accessors are coming from. – MCH May 10 '18 at 09:10