2013-03-13 2 views
2

У меня есть две функции в моей программе:Шаблон типа данных в определении функции

getWidth :: Size -> GLint 
getWidth (Size a b) = a 

getXPos :: Position -> GLint 
getXPos (Position a b) = a 

я понял, что эти две функции делают то же самое, и единственным отличием является тип параметра. Вопрос: как я написать такую ​​обобщенную функцию:

getFirst :: ANYTHING -> a 
getFirst (ANYTHING a b) -> a 

ответ

5

Вам нужен type class (хотя ИМО это не очень хорошая идея, чтобы обобщить эти две функции):

class Dimension d where 
    getX :: d -> GLint 
    getY :: d -> GLint 

instance Dimension Size where 
    getX (Size x y) = x 
    getY (Size x y) = y 

instance Dimension Position where 
    getX (Position x y) = x 
    getY (Position x y) = y 

Если вы только хочу писать меньше кода, использовать record syntax:

data Size = Size { getWidth :: GLint, getHeight :: GLint } 
data Position = Position { getXPos :: GLint, getYPos :: GLint } 
7

Это, вероятно, немного излишним для вашей проблемы, но, возможно, это будет полезно Ф.О. кто-то другой, который наткнулся на этот вопрос.

Вы можете реализовать действительно универсальную функцию, которая работает с любым типом данных, который имеет один конструктор с двумя полями, используя GHC's generic programming.

Давайте сначала посмотрим на подпись типа. Вы хотели бы написать функцию, такую ​​как

getFirst :: ANYTHING -> a 

В Haskell, тип, который может быть «ничего» означается с типом переменной (так же, как тип результата a), так что давайте писать

getFirst :: t -> a 

Однако, имея полностью полиморфный тип, мы не можем работать с типом в , так как мы не можем делать никаких предположений о его внутренней структуре. Поэтому нам нужно написать в некоторых ограничениях о типе t.

Вторая вещь в том, что полиморфный тип возвращаемого значения (a выше) означает, что тип возвращение выводится на основе сайта вызова, по существу, это означает, что абонент может на «запрос» любой возможный тип для первого поле. Это, очевидно, невозможно, , поскольку, например, для Size единственным допустимым типом возврата является GLint. Поэтому нам нужно , чтобы объявить тип возврата так, чтобы он зависел от типа t.

getFirst :: (Generic t, GPair (Rep t)) => t -> FirstT (Rep t) 

Теперь, это довольно сложный вид подпись, но суть в том, что для любого типа t, что является общим и имеет общие представления Rep t, которое является допустимой, родовой пара (GPair), мы можем получить доступ к первому полю пары , который имеет тип FirstT (Rep t).

Типа класс GPair может быть определен как этот

class GPair g where 
    type FirstT g -- type of the first field in the pair 
    type SecondT g -- type of the second field in the pair 

    gGetFirst :: g x -> FirstT g 
    gGetSecond :: g x -> SecondT g 

Этого типа класс вводит функцию gGetFirst и gGetSecond, которые не работать на самом типе пары, но его общего представление.Тип delcarations FirstT и SecondT являются так называемыми ассоциированными типами синонимов, которые являются частью расширения языка TypeFamilies. То, что мы заявляем здесь , состоит в том, что FirstT и SecondT являются синонимами для существующего неизвестного типа , который определяется по типу g.

Обобщенные представления типов завернуты в описаниях мета-данных, которые содержат информацию, такую ​​как имя типа данных, имена конструктора, запись поля имена и т.д. Мы не будем нуждаться в какой-либо из этой информации для этого случая, поэтому первый экземпляр GPair просто удаляет слой метаданных.

instance GPair f => GPair (M1 i c f) where 
    type FirstT (M1 i c f) = FirstT f 
    type SecondT (M1 i c f) = SecondT f 

    gGetFirst = gGetFirst . unM1 
    gGetSecond = gGetSecond . unM1 

Далее нам нужно сделать экземпляр для общего конструктора с двумя полями.

instance (GField l, GField r) => GPair (l :*: r) where 
    type FirstT (l :*: r) = FieldT l 
    type SecondT (l :*: r) = FieldT r 

    gGetFirst (l :*: _) = gGet l 
    gGetSecond (_ :*: r) = gGet r 

И тогда мы определим общее поле типа класса GField, который работает на одного поля пары.

class GField g where 
    type FieldT g 

    gGet :: g x -> FieldT g 

Мы раздеть слой мета-данных из GField, как мы делали выше

instance GField f => GField (M1 i c f) where 
    type FieldT (M1 i c f) = FieldT f 

    gGet = gGet . unM1 

А теперь нам просто нужно добавить экземпляр общих полей конструктора.

instance GField (K1 r t) where 
    type FieldT (K1 r t) = t 

    gGet (K1 x) = x 

Теперь мы можем реализовать действительно общие функции аксессоры getFirst и getSecond.

getFirst :: (Generic t, GPair (Rep t)) => t -> FirstT (Rep t) 
getFirst = gGetFirst . from 

getSecond :: (Generic t, GPair (Rep t)) => t -> SecondT (Rep t) 
getSecond = gGetSecond . from 

Функция from является частью GHC.Generics и преобразует значение в его общей форме. Для этого типам данных Size и Position необходимо реализовать Generic тип-класс.

{-# LANGUAGE DeriveGeneriC#-} 

data Position = Position GLInt GLInt deriving Generic 
data Size  = Size GLInt GLInt deriving Generic 

Давайте проверим это:

> let sz = Size 1 2 
> let pos = Position 4 6 
> getFirst sz 
1 
> getSecond pos 
6 

функция также работает автоматически для подходящего встроенных типов, таких как кортежей:

> getSecond (1, "foo") 
"foo" 

Теперь, вы можете подумать, что это это очень много кода для простой, общей функции , и это является серьезной проблемой. Однако на практике типичные экземпляры довольно легко и быстро записываются, как только вы знакомы с тем, как структурируются типы обобщенного представления.

Кроме того, главное в программировании GHC заключается в том, что он полностью безопасен по типу (в отличие от, например, API-интерфейсов отражения в Java). Это означает, что если вы попытаетесь использовать общие функции с несовместимыми типами, вы получите ошибку времени компиляции вместо исключение во время выполнения.

Например:

a = getFirst (1,2,3) -- compile error because value has more than two fields 

data Foo = Foo Int Int | Bar Float Float deriving Generic 

b = getFirst $ Foo 1 2 -- compile error because the type has multiple constuctors 

Вот полный код пытается это:

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE TypeFamilies #-} 
{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE TypeOperators #-} 
{-# LANGUAGE DeriveGeneriC#-} 

import GHC.Generics 

class GPair g where 
    type FirstT g 
    type SecondT g 

    gGetFirst :: g x -> FirstT g 
    gGetSecond :: g x -> SecondT g 

instance GPair f => GPair (M1 i c f) where 
    type FirstT (M1 i c f) = FirstT f 
    type SecondT (M1 i c f) = SecondT f 

    gGetFirst = gGetFirst . unM1 
    gGetSecond = gGetSecond . unM1 

instance (GField l, GField r) => GPair (l :*: r) where 
    type FirstT (l :*: r) = FieldT l 
    type SecondT (l :*: r) = FieldT r 

    gGetFirst (l :*: _) = gGet l 
    gGetSecond (_ :*: r) = gGet r 

class GField g where 
    type FieldT g 

    gGet :: g x -> FieldT g 

instance GField f => GField (M1 i c f) where 
    type FieldT (M1 i c f) = FieldT f 

    gGet = gGet . unM1 

instance GField (K1 r t) where 
    type FieldT (K1 r t) = t 

    gGet (K1 x) = x 

getFirst :: (Generic t, GPair (Rep t)) => t -> FirstT (Rep t) 
getFirst = gGetFirst . from 

getSecond :: (Generic t, GPair (Rep t)) => t -> SecondT (Rep t) 
getSecond = gGetSecond . from 
Смежные вопросы