Обобщенное программирование в Haskell

Роман Чепляка, Signal Vine

Обобщенное программирование в Haskell

Роман Чепляка

Типы 60х

FUNCTION toUnixTime(year, month, day)
   INTEGER :: toUnixTime
   INTEGER :: year
   INTEGER :: month
   INTEGER :: day
   ... 
END FUNCTION

Типы 70х

struct Date {
  int year;
  int month;
  int day;
};

int toUnixTime(struct Date d);

Типы 90х

newtype Year  = Year Int
newtype Month = Month Int
newtype Day   = Day Int
newtype Unix  = Unix Int
data Date     = Date Year Month Day

toUnixTime :: Date -> Unix

Как снизить стоимость новых типов?

Standard deriving

data Date = Date Year Month Day
  deriving (Eq, Ord, Show)

Newtype deriving

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Year = Year Int
  deriving ( Eq, Ord, Show,
             Real, Integral, Num,
             Serial, ToJSON, FromJSON )

Template Haskell

{-# LANGUAGE TemplateHaskell #-}

data Date = Date Year Month Day
deriveJSON ''Date

Template Haskell

deriveJSON :: Name -> Q [Dec]
deriveJSON name = do
  {- ... -}

Generic programming

Generic view

data Date = Date Year Month Day
type DateView = (Int, Int, Int)

Generic view: isomorphism

toGeneric :: Date -> DateView
toGeneric (Date (Year y) (Month m) (Day d)) = (y, m, d)
fromGeneric :: DateView -> Date
fromGeneric (y, m, d) = (Date (Year y) (Month m) (Day d))

Generic equality

dateEq :: Date -> Date -> Date
dateEq d1 d2 =
  toGeneric d1 == toGeneric d2

Comparison

Query

Transform

Generation

Generalized generic view

class Generic a where
  toGeneric :: a -> View
  fromGeneric :: View -> a

genericEq :: Generic a => a -> a -> a
genericEq x y =
  toGeneric x == toGeneric y

Generalized generic view

class Generic a where
  type Rep a
  toGeneric :: a -> Rep a
  fromGeneric :: Rep a -> a

genericEq :: (Generic a, Eq (Rep a)) => a -> a -> a
genericEq x y =
  toGeneric x == toGeneric y

Simple representation

instance Generic Date where
  type Rep Date = (Int, (Int, Int))

Example: Hashable

class Hashable a where hash :: a -> Int
instance Hashable Int where
  hash n = n `mod` 13
instance Hashable (a,b) where
  hash (a,b) = 7 * hash a + hash b

genericHash :: (Generic a, Hashable (Rep a)) => a -> Int
genericHash x = hash (toGeneric x)

Example: Hashable

instance Hashable (Either a b) where
  hash (Left a)  = hash (a, 1)
  hash (Right b) = hash (b, 2)

Still missing

Show/Read/JSON

data Maybe a = Nothing | Just a

class Functor f where
  fmap :: (a -> b) -> (f a -> f b)

GHC Generics

GHC Generics: representation

data (:+:) f g p = L1 (f p) | R1 (g p)
data (:*:) f g p = f p :*: g p
data U1 p = U1
data V1 p
newtype Par1 p = Par1 p
newtype K1 i c p = K1 c
newtype (f :.: g) p = Comp1 { unComp1 :: f (g p) }

GHC Generics: class

class Generic1 f where
  type Rep1 f :: * -> *
  from1  :: f a -> Rep1 f a
  to1    :: Rep1 f a -> f a

Maybe example

data Maybe a = Nothing | Just a

instance Generic1 Maybe where
  type Rep1 f = U1 :+: Par1

Generic functor

instance Functor U1 where
  fmap _ U1 = U1
instance Functor Par1 where
  fmap f (Par1 a) = Par1 (f a)
instance Functor (K1 i c) where
  fmap _ (K1 a) = K1 a
instance (Functor f) => Functor (Rec1 f) where
  fmap f (Rec1 a) = Rec1 (fmap f a)
instance (Functor f, Functor g) => Functor (f :+: g) where
  fmap f (L1 a) = L1 (fmap f a)
  fmap f (R1 a) = R1 (fmap f a)
instance (Functor f, Functor g) => Functor (f :*: g) where
  fmap f (a :*: b) = fmap f a :*: fmap f b
instance (Functor f, Functor g) => Functor (f :.: g) where
  fmap f (Comp1 x) = Comp1 (fmap (fmap f) x)

Generic functor

genericFmap :: Generic1 f => (a -> b) -> (f a -> f b)
genericFmap f = to1 . fmap f . from1

Non-functorial representation

data Maybe a = Nothing | Just a

instance Generic (Maybe a) where
  type Rep f = U1 :+: K1 R a

Metainformation

data M1 f p = M1 Info (f p)

data Info
  = DatatypeName String
  | ConstructorName String
  | SelectorName String

Metainformation

data M1 f p = M1 (f p)

Metainformation

data M1 i c f p = M1 (f p)

Metainformation

data M1 i c f p = M1 (f p)

i — один из:

data D
data C
data S

Metainformation

class Datatype c where
  datatypeName :: t c f p -> String
  moduleName   :: t c f p -> String
  isNewtype    :: t c f p -> Bool

Metainformation

type Rep1 Maybe =
  M1 D D1Maybe
    (M1 C C1_0Maybe U1
      :+: 
     M1 C C1_1Maybe (M1 S NoSelector Par1))

Representation

-- option 1
type Rep Date = Int :*: Int :*: Int
-- option 2
type Rep Date = Year :*: Month :*: Date

Other approaches