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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With