Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Current state of record types and subtyping in Haskell

Tags:

haskell

What's the current state of record types and subtyping in Haskell?

I know there's been work done on things like overloaded record names, etc. Specifically, I'd like to make three different record types A, B, and C where B and C contain all of the same field labels as A, but do not share field labels with each other. Then, I'd like to be able to write functions where f : A -> int, g: B -> int, h: C -> int where the function f also accepts arguments with types B and C. Basically, I want B and C to be subtypes of A. More specifically, it'd be nice if I didn't have to repeat all of the field labels. In pseudo code, this is something like

data A = A { a :: String }
data B = B { A, b :: Char }
data C = C { C, c :: Float }

f :: A/B/C -> int
g :: B -> int
h :: C -> int
like image 993
wyer33 Avatar asked Mar 14 '14 22:03

wyer33


People also ask

Is it possible to use a Newtype with a record type?

Record syntax can be used with newtype with the restriction that there is exactly one constructor with exactly one field. The benefit here is the automatic creation of a function to unwrap the newtype. These fields are often named starting with run for monads, get for monoids, and un for other types.

What are record accessors in Haskell?

In this example, two record accessors are defined, age and name, which allow us to access the age and name fields respectively. Record accessors are just Haskell functions which are automatically generated by the compiler. As such, they are used like ordinary Haskell functions.

What are field labels used for in Haskell?

As such, they are used like ordinary Haskell functions. By naming fields, we can also use the field labels in a number of other contexts in order to make our code more readable. lowerCaseName :: Person -> String lowerCaseName (Person { name = x }) = map toLower x

Is there a special syntax for updating data types with labels?

There is also special syntax for updating data types with field labels. a new value of type Person can be created by copying from alex, specifying which values to change: Record syntax can be used with newtype with the restriction that there is exactly one constructor with exactly one field.


3 Answers

There isn't one. There is a proposal to add overloaded records fields and a working implementation but AFAIK it hasn't been merged into GHC's head yet. You can read about the proposal here. Once it lands we'll have something similar to rho polymorphism but automagically generated/inferred type classes. Notice this isn't subtyping. {a :: Int, b :: Bool} <: {a :: Int} isn't a concept in Haskell, instead we'd be able to say something like

 foo :: r {a :: Int} -> Int
 foo = a

which will really be something more like

 foo :: Has "a" Int r => r -> Int
 foo = a

If instead we wrote something like

 foo :: {a :: Int} -> {a :: Int}
 foo = id

And wanted this to behave like we had subtyping, we could do something like

 foo _ = A {a = 1}

and return any type that's a subtype of {a :: Int}.

There are alternatives as library, like Vinyl and to a certain extent, lens. I'd suggest investigating these for now especially if you want any compatibility with 7.6/7.8.

like image 61
Daniel Gratzer Avatar answered Sep 22 '22 20:09

Daniel Gratzer


There are a few different ways of accomplishing this along with a few downsides.

Here are the types I'm working with:

data A = A { a :: String}
data B = B { bA :: A, b :: Char}
data C = C { cA :: A, c :: Float}

Ad-hoc polymorphism: All these types can f

You can define f as a method of some CanF class that A, B, and C all are instances of:

class CanF a where
  f :: a -> Int

instance CanF A where
  f = length . a

instance CanF B where
  f = f . bA

instance CanF C where
  f = f . cA

Defining B and C's instances in terms of A's instance makes it clear that f does the same thing in each case. It's easy to make f do different things depending on which type defines the F instance. The disadvantage to this approach is that any other f-like functions will need to be added as methods of the same "CanSomething" class.

main :: IO ()
main = do
    print (f a)
    print (f b)
    print (f c)
  where
    a = A "Hello"
    b = B a 'H'
    c = C a 3.14

Ad-hoc polymorpshism: All these types can be represented as an A

Another approach is to write f as a function constrained by a class that always gives you an A.

class RepA a where
  getA :: a -> A

instance RepA A where
  getA = id

instance RepA B where
  getA = bA

instance RepA C where
  getA = cA

f :: RepA a => a -> Int
f = length . a . getA

Here you have less flexibility for defining what f can do, which might be good or bad. The advantage is that you can define other functions which work on A's without adding new methods to your class.

Record of functions

My preferred way to handle this is the record-of-functions approach. Define a parameterized data type which holds functions you want to call. Then define specialized constructors for your record type. The disadvantage of this approach is that it is often more verbose. The advantage is that you can swap out behaviors by supplying a different F to the f function. Another advantage is that you can accomplish more without requiring language extensions.

data F a = F { f :: a -> Int }

af :: F A
af = F $ length . a

bf :: F B
bf = F $ f af . bA

cf :: F C
cf = F $ f af . cA

main :: IO ()
main = do
    print (f af a)
    print (f bf b)
    print (f cf c)
  where
    a = A "Hello"
    b = B a 'H'
    c = C a 3.14
like image 26
Michael Steele Avatar answered Sep 22 '22 20:09

Michael Steele


Since the HasField class is in GHC's head branch, with GHC's development version 8.2.0.20170310, we get a working example of polymorphism on records with specific fields, which can be used with handwritten structural subtyping:

{-# LANGUAGE DuplicateRecordFields, DataKinds, FlexibleContexts, TypeApplications #-}

import GHC.Records (HasField(getField))

data A = A { a :: String }
data B = B { a :: String, b :: Char }
data C = C { a :: String, c :: Float }

-- | length of field "a"

f :: HasField "a" rec String => rec -> Int
f = length . getField @"a"

main = do
    print $ f $ A "a"
    print $ f $ B "b" 'b'
    print $ f $ C "c" 1.5

Documented in "GHC 8.2 branch" users guide (https://github.com/ghc/ghc/blob/ghc-8.2/docs/users_guide/glasgow_exts.rst) search for "Record field selector polymorphism"

GHC's 8.2.1 development binary from Herbert Riedel's Ubuntu PPA (https://launchpad.net/~hvr/+archive/ubuntu/ghc/+index?batch=150)

like image 34
Gabriel Riba Avatar answered Sep 26 '22 20:09

Gabriel Riba