What do bracketed double dots mean in Haskell?
Asked Answered
M

1

10

I understand that .. can be used in ranges--i.e., [1..3] == [1,2,3], and [10..] is an infinite list starting at 10.

However, recently I've started seeing these double dots inside brackets too. Either as (..) or {..}.

For example, an import statement can read import Colog (HasLog (..))

My first intuition here is to think that this means that HasLog has several components and we are explicitly importing all of them. But how does that differ from simply importing HasLog, without the (..)?

Further, how does (..) differ from {..}?

Machzor answered 30/6, 2021 at 10:30 Comment(0)
Q
10

(..) and {..} are both ways to bring names associated with data types in scope, but they're very different and used in different contexts.

  • The (..) syntax is specifically used for import and export lists. It's part of the syntax for declaring which constructors and/or record fields of a data type you want to import/export. For example, if I have a module

    module FooM where
    data Foo = F0 | F1 | F2
    data Bar = Bar { b0 :: Int, b1 :: Char }
    

    then this compiles

    import FooM ( Foo(F1,F2), Bar(b1) )
    fu = F1
    ba = b1
    

    but this doesn't

    import FooM ( Foo(F1,F2) )
    fu = F0
    

    because I've only imported the F1 and F2 constructors, not F0. If I write

    import FooM ( Foo(..), Bar(..) )
    fu = F0
    ba = b0
    

    it also works, because this imports all constructors and record labels. By contrast, with

    import FooM ( Foo )
    fu = F0
    

    you don't import any constructors or record fields at all, but only Foo as an opaque type, so here fu = F0 also won't compile. (This often exploited in export lists, if you want the inner structure of a data type to be “private” and only manipulable with utility functions, smart constructors etc..)

  • {..} is part of the RecordWildCards extension. In a nutshell, it turns the names of all record labels that would apply to a parameter into locally-scoped variables.
    I would advice against using this, it's one of those extensions that tried to address the shortcomings of Haskell's record system – with IMO not much success. Better to either use records in the traditional way, or auto-generate some lenses and use these to completely avoid the record issues.

Quinquepartite answered 30/6, 2021 at 10:42 Comment(2)
If I may ask: do you mind sharing a little bit about the intuition behind {..} not being a very good solution to the shortcomings of the record system? Why do you recommend against using it?Machzor
The main issue is that it leads to confusion, because it's not obvious from looking at a small piece of code whether you see b0 :: Bar -> Int or a RecordWildCards-introduced b0 :: Int. And furthermore, if you're dealing with multiple Bar values, which one does it come from? {..} kind of mimics the way how in many OO languages, class fields are automatically in scope in methods of that class. But in that case it's obvious what the “containing object” is you're referring to. In Haskell, there's no such thing so the approach doesn't work very well.Quinquepartite

© 2022 - 2024 — McMap. All rights reserved.