Sunday, October 26, 2025

[blyqokgn] simultaneously define record type and data

we propose a Haskell syntax extension which may be useful when a record type has very few data values of that type, perhaps just one, e.g., the Singleton design pattern.

introduce the keyword DEFINERECORD:

singletonrecord :: Recordtype;
singletonrecord = DEFINERECORD RecordType RecordConstructor { john :: Int = 1, paul :: String = "bass", george :: Bool = True, ringo :: Float = 1.0};

the benefit of this syntax is that the field types and their corresponding values get defined next to each other.  future code changes to type and value will happen at the same place.  there is less danger of accidentally leaving a field uninitialized.

also, the field labels are each written exactly once (Don't Repeat Yourself), in contrast to the current method (below), defining the data type then defining the singleton record using record syntax, which requires typing each field label twice.  although you can optionally put type annotations on field values so that field, value, and type are next to each other when defining the singleton, it feels like even more Repeating Yourself.

data RecordType = RecordConstructor { john :: Int, paul :: String, george :: Bool, ringo :: Float };
singletonrecord :: Recordtype;
singletonrecord = RecordConstructor { john = 1 :: Int, paul = "bass" :: String, george = True :: Bool, ringo = 1 :: Float};

the proposed syntax cannot be used if RecordType has multiple constructors.  (because there can only be one constructor, also consider simpler syntax which restricts the constructor to be the same as that of the type.)

if you have nested records, the syntax can define inner record types "in place" as well.

singletonrecord :: Recordtype;
singletonrecord = DEFINERECORD RecordType RecordConstructor { john :: Int = 1, paul :: String = "bass", george :: Bool = True, ringo = DEFINERECORD DrumType DrumConstructor { snare :: Double = 1.0, cymbal :: String = "crash" } };

this might be a nice feature to combine with Data.Default .

slight variation: record types can be unnamed (anonymous), but have named accessor functions (named fields).  introduce the keyword UNNAMEDRECORD, which stands for both type and constructor.

f1 :: UNNAMEDRECORD;
f1 = UNNAMEDRECORD { john :: String = "guitar", paul :: String = "bass", george :: String = "guitar", ringo = UNNAMEDRECORD { snare :: Double = 1.0, cymbal :: String = "crash" } };

f2 :: UNNAMEDRECORD;
f2 = UNNAMEDRECORD { capital :: String = "london", home :: String = "liverpool" };

g :: String;
g = let
{ v1 :: UNNAMEDRECORD
; v1 = f1
; v2 :: UNNAMEDRECORD
; v2 = f2
} in cymbal (ringo v1) ++ home v2;
-- returns "crashliverpool"

this is better than returning multiple values through tuple syntax because components get named, both when creating and extracting.  using names seems less error-prone than extracting tuple components by position:

g = let { v1 = f1 ; v2 = f2 } in (case v1 of {(_,_,_, (_,x) )->x}) ++ snd v2

open question: if we use DEFINERECORD or UNNAMEDRECORD inside a let or where, should the type name and accessor functions escape the let and become visible in the global scope?  perhaps explicitly mark things for export from the let (requires more new syntax).

more sophistication (and complexity) possible: lenses, record wildcards and puns, etc.  type inference probably becomes more difficult.

related work by Alexander Thiemann: SuperRecord anonymous records.

although we propose this extension for Haskell, it seems a nice feature to have in any programming language.

3 comments :

Anonymous said...

'Stand-alone' records are so useful nearly every other Haskell-alike language has them. You're trying to be DRY, then why introduce a useless new type name with a useless new data constr?
There's already a Haskell that supports first-class
`single = { john = 1 :: Int, paul = "bass" :: String, george = True :: Bool, ringo = 1 :: Float}`
(modulo a bit of syntax, with all the type theory worked out)

Anonymous said...

Which Haskell would that be?

Anonymous said...

As described at https://web.cecs.pdx.edu/~mpj/pubs/96-3.pdf; download instructions https://mailman.haskell.org/archives/list/hugs-bugs@haskell.org/thread/SRBXTGKY7CXF4V7IJFXPI3LYW3EPDNAI/
AntC (I'm not trying to hide behind Anonymous)