wiki:StricterLabelledFieldSyntax

Version 2 (modified by igloo, 5 years ago) (diff)

--

Proposal: StricterLabelledFieldSyntax

Ticket #132
Dependencies none
Related none

Compiler support

GHC partial (test patch available)
nhc98 none
Hugs none
UHC none
JHC none
LHC none

Summary

Make the labelled field syntax stricter, so that unclear code is illegal.

Description

Many people believe that the precedence of labelled fields creation, updates and pattern matching can lead to confusing code. For example, in http://hackage.haskell.org/trac/ghc/ticket/2530 it was reported that this program:

module Main where

data A = A {x :: Int} deriving (Show)

main :: IO ()
main = print $ Just A {x = 5}

(correctly, according to Haskell 98) prints

Just A {x = 5}

in hugs, but the more easily comprehendible

Just (A {x = 5})

in ghci.

According to Haskell 98,

A {x = 5}

is an atomic expression - but it doesn't look atomic! This violates the principle of least surprise.

Before coming across labelled fields, but having had "function application binds tightest" drummed into you to understand how to read expressions containing a mixture of applications and infix operators, of the following 2 functions:

data A = A {x :: Bool} deriving (Show)

f = print $ Just A {x = True}
g = print $ A True {x = False}

I would expect g to be the correct one, while it is f that is correct according to Haskell 98.

However, once you remove the space before the curly brace:

h = print $ Just A{x = True}
i = print $ A True{x = False}

it is h that looks more correct. Note that we have a similar problem with infix operators, and expressions like

1+f x+2

I propose that all of f, g, h and i be made illegal, with parentheses being required to disambiguate these cases.

Here are some real-life examples of what I consider confusing code, from haskeline:

metaKey :: Key -> Key
metaKey (Key m bc) = Key m {hasMeta = True} bc
searchText :: SearchEntry -> [Grapheme]
searchText SearchEntry {entryState = IMode xs ys} = reverse xs ++ ys

and from Cabal:

configure ... = do
  ...
  (ghcPkgProg, ghcPkgVersion, conf'') <-
    requireProgramVersion verbosity ghcPkgProgram {
      programFindLocation = guessGhcPkgFromGhcPath ghcProg
    }
    anyVersion (userMaybeSpecifyPath "ghc-pkg" hcPkgPath conf')
  ...
buildExe :: Verbosity -> PackageDescription -> LocalBuildInfo
                      -> Executable         -> ComponentLocalBuildInfo -> IO ()
buildExe verbosity _pkg_descr lbi
  exe@Executable { exeName = exeName', modulePath = modPath } clbi = do
  ...
      case PackageIndex.lookupPackageName index (PackageName "rts") of
        [rts] -> PackageIndex.insert rts { Installed.ldOptions = [] } index
register pkg@PackageDescription { library       = Just lib  }
         lbi@LocalBuildInfo     { libraryConfig = Just clbi } regFlags
    = ...

References

A patch to add support for the syntax to GHC, as well as patches needed to fix GHC and the libraries to follow the new syntax, are in the ticket (#132).

Report Delta

In Section 3 replace:

exp10 -> ...
       | fexp

with:

exp10 -> ...
       | recexp
recexp -> qcon { fbind1 , ... , fbindn }        (labeled construction, n >= 0)
        | aexp<qcon> { fbind1 , ... , fbindn }  (labeled update, n >= 1) 
        | fexp

and remove:

aexp -> ...
      | qcon { fbind1 , ... , fbindn }        (labeled construction, n>=0)
      | aexp<qcon> { fbind1 , ... , fbindn }  (labeled update, n >= 1)

In Section 3.15.2 replace:

aexp -> qcon { fbind1 , ... , fbindn }        (labeled construction, n>=0)

with:

recexp -> qcon { fbind1 , ... , fbindn }        (labeled construction, n >= 0)

In Section 3.15.3 replace:

aexp -> aexp<qcon> { fbind1 , ... , fbindn }  (labeled update, n >= 1)

with:

recexp -> aexp<qcon> { fbind1 , ... , fbindn }  (labeled update, n >= 1)

In Section 3.17.1 replace:

pat10 -> ...

with:

pat10 -> ...
       | qcon { fpat1 , ... , fpatk }    (labeled pattern, k >= 0)

and remove:

apat -> ...
      | qcon { fpat1 , ... , fpatk }    (labeled pattern, k>=0)

In Section 9.5 replace:

exp10 -> ...
       | fexp

with:

exp10 -> ...
       | recexp
recexp -> qcon { fbind1 , ... , fbindn }        (labeled construction, n >= 0)
        | aexp<qcon> { fbind1 , ... , fbindn }  (labeled update, n >= 1) 
        | fexp

and remove:

aexp -> ...
      | qcon { fbind1 , ... , fbindn }        (labeled construction, n>=0)
      | aexp<qcon> { fbind1 , ... , fbindn }  (labeled update, n >= 1)

and replace

pat10 -> ...

with:

pat10 -> ...
       | qcon { fpat1 , ... , fpatk }    (labeled pattern, k >= 0)

and remove:

apat -> ...
      | qcon { fpat1 , ... , fpatk }    (labeled pattern, k>=0)