[Haskell] dictionary-passing style
От: deniok Россия  
Дата: 05.01.07 13:01
Оценка: 4 (1)
Нашёл классное место в черновом варианте статьи “A History of Haskell” отцов-основателей Хаскелла (Paul Hudak, John Hughes, Simon Peyton Jones, Philip Wadler) к конференции “2007 History of Program-ming Languages Conference” (HOPL-III). К сожалению, черновик сейчас уже недоступен.

Собственно идея в том, что классы типов, несмотря на свою выразительную мощь, универсальным образом могут быть оттранслированы в обычные объявления типов. Я думаю, разработчики компиляторов приблизительно так и поступают.

Вот кусок статьи, ниже – перевод:

6.1 Type classes
The basic idea of type classes is simple enough. Consider equality, for example. In Haskell we may write

Class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

instance Eq Int where
  i1 == i2 = eqInt i1 i2
  i1 /= i2 = not (i1 == i2)

instance (Eq a) => Eq [a] where
  []     == []     = True
  (x:xs) == (y:ys) = (x == y) && (xs == ys)
  xs /= ys = not (xs == ys)

member Eq a => a -> [a] -> Bool
member x []                 = False
member x (y:ys) | x == y    = True
                | otherwise = member x ys


The type signature for member uses a form of bounded quantification: it declares that member has type a->[a]->Bool, for any type a that is an instance of the class Eq. A class declaration specifies the methods of the class (just two in this case, namely (==) and (/=)) and their types. A type is made into an instance of the class using an instance declaration, which provides an implementation for each of the class’s methods, at the appropriate instance type.
A particularly attractive feature of type classes is that they can be translated into so-called “dictionary-passing style”, by a type-directed transformation. Here is the translation of the above code:

data Eq a = MkEq (a->a->Bool) (a->a->Bool)
eq (MkEq e _) = e
ne (MkEq _ n) = n

dEqInt :: Eq Int
dEqInt =  MkEq eqInt (not . eqInt)

dEqList :: Eq a -> Eq [a]
dEqList (MkEq e _) =  MkEq el (not . el)
    where el []     []     = True
          el (x:xs) (y:ys) = x `e` y && xs `el` ys

member :: Eq a -> a -> [a] -> Bool
member d x [] = False
member d x (y:ys) | eq d x y  = True
                  | otherwise = member d x ys


The class declaration translates to a data type declaration, which declares a dictionary for Eq; that is, a record of its methods. The functions eq and ne select the equality and inequality method from this diction-ary. The member function takes a dictionary parameter of type Eq a, corresponding to the Eq a constraint in its original type, and performs the membership test by extracting the equality method from this dictionary using eq. Finally, an instance declaration translates to a function that takes some dictionaries and returns a more complicated one. For example, dEqList takes a dictionary for Eq a and returns a dictionary for Eq [a].


Перевод:


6.1 Классы типов
Основная идея классов типов достаточно проста. Рассмотрим, например, равенство. В Хаскелле мы можем написать:

Class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

instance Eq Int where
  i1 == i2 = eqInt i1 i2
  i1 /= i2 = not (i1 == i2)

instance (Eq a) => Eq [a] where
  []     == []     = True
  (x:xs) == (y:ys) = (x == y) && (xs == ys)
  xs /= ys = not (xs == ys)

member Eq a => a -> [a] -> Bool
member x []                 = False
member x (y:ys) | x == y    = True
                | otherwise = member x ys


Сигнатура типа для member использует форму ограниченного квантора: она объявляет, что member имеет тип a->[a]->Bool, для любого типа a, являющегося экземпляром класса Eq. Объявление class определяет методы этого класса (в данном случае два метода, а именно (==) и (/=)) и их типы. Тип становится экземпляром класса с помощью объявления instance, которое обеспечивает реализацию каждого метода класса, подходящую для типа экземпляра.
Особенно привлекательным свойством классов типов является возможность их трансляции в так называемый “dictionary-passing style” через типо-ориентированное преобразование (type-directed transformation). Вот трансляция предыдущего кода:

data Eq a = MkEq (a->a->Bool) (a->a->Bool)
eq (MkEq e _) = e
ne (MkEq _ n) = n

dEqInt :: Eq Int
dEqInt =  MkEq eqInt (not . eqInt)

dEqList :: Eq a -> Eq [a]
dEqList (MkEq e _) =  MkEq el (not . el)
    where el []     []     = True
          el (x:xs) (y:ys) = x `e` y && xs `el` ys

member :: Eq a -> a -> [a] -> Bool
member d x [] = False
member d x (y:ys) | eq d x y  = True
                  | otherwise = member d x ys


Объявление class переводится в объявление типа data, которое объявляет словарь (dictionary) для Eq, то есть запись (record) из его методов. Функции-селекторы eq и ne выбирают методы равенства и неравенства из этого словаря. Функция member принимает параметр-словарь типа Eq a, соответствующий ограничению Eq a своего исходного типа, и выполняет проверку на членство (значения в списке), выделяя метод равенства из этого словаря с помощью eq. Наконец, объявления instance транслируются в функции, принимающие некоторый словарь и возвращающие более сложный словарь. Например, dEqList принимает словарь для Eq a, а возвращает словарь для Eq [a].


Работает это так (Сессия Hugs):

Eq2> member dEqInt 2 [3,5,2]
True
Eq2> member dEqInt 2 [3,5,7]
False
Eq2> member (dEqList dEqInt) [3,5] [[4],[1,2,3],[3,5]]
True
Eq2> member (dEqList dEqInt) [3,5] [[4],[1,2,3],[3,8]]
False
Re: [Haskell] dictionary-passing style
От: palm mute  
Дата: 05.01.07 13:38
Оценка:
deniok wrote:

>

> Собственно идея в том, что классы типов, несмотря на свою выразительную
> мощь, универсальным образом могут быть оттранслированы в обычные
> объявления типов. Я думаю, разработчики компиляторов приблизительно так
> и поступают.
>

Мне этот фрагмент тоже понравился. Первой реакцией было — сейчас сделаю
перегрузку для окамла на Камлп4! Но сразу стало ясно, что не зная
типа, правильный словарь не передать. Так что любопытно, конечно, но
если ты не разрабатываешь компилятор, применить это знание некуда.

Возможно, можно на макросах прикрутить к пресловутому Н. классы типов,
но там перегрузка уже и так есть .
Posted via RSDN NNTP Server 2.0
Re[2]: [Haskell] dictionary-passing style
От: deniok Россия  
Дата: 05.01.07 13:51
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>Мне этот фрагмент тоже понравился. Первой реакцией было — сейчас сделаю

PM>перегрузку для окамла на Камлп4! Но сразу стало ясно, что не зная
PM>типа, правильный словарь не передать.

Можно по-подробнее, что значит "не зная типа"?
Re[3]: [Haskell] dictionary-passing style
От: palm mute  
Дата: 05.01.07 14:08
Оценка:
deniok wrote:

>

> Здравствуйте, palm mute, Вы писали:
>
> PM>Мне этот фрагмент тоже понравился. Первой реакцией было — сейчас сделаю
> PM>перегрузку для окамла на Камлп4! Но сразу стало ясно, что не зная
> PM>типа, правильный словарь не передать.
>
> Можно по-подробнее, что значит "не зная типа"?
Camlp4 отдает мне AST. Глядя на узел дерева, я могу сказать, что это —
выражение, образец, модуль или что-то другое. Для применения описанной в
статье трансформации я должен каждый вызов функции заменить вызовом
трансформированной функции, и первым аргументом ей передать
соответствующий типу второго аргумента словарь (например, dEqInt или
dEqList dEqInt). Но (в случае Камлп4) типа аргумента я не знаю! И чтобы
узнать, нужно самому делать вывод типов .
Posted via RSDN NNTP Server 2.0
Re[4]: [Haskell] dictionary-passing style
От: deniok Россия  
Дата: 05.01.07 14:14
Оценка:
Здравствуйте, palm mute, Вы писали:

Спасибо, задачу понял
Re[2]: [Haskell] dictionary-passing style
От: Alexey Romanov  
Дата: 06.01.07 05:12
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>deniok wrote:


>>

>> Собственно идея в том, что классы типов, несмотря на свою выразительную
>> мощь, универсальным образом могут быть оттранслированы в обычные
>> объявления типов. Я думаю, разработчики компиляторов приблизительно так
>> и поступают.
>>

PM>Мне этот фрагмент тоже понравился. Первой реакцией было — сейчас сделаю

PM>перегрузку для окамла на Камлп4! Но сразу стало ясно, что не зная
PM>типа, правильный словарь не передать. Так что любопытно, конечно, но
PM>если ты не разрабатываешь компилятор, применить это знание некуда.

PM>Возможно, можно на макросах прикрутить к пресловутому Н. классы типов,

PM>но там перегрузка уже и так есть .

Создатели Nemerle говорят, что можно, но сложно.
Re[3]: [Haskell] dictionary-passing style
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.01.07 06:46
Оценка: :)
Здравствуйте, Alexey Romanov, Вы писали:

AR>Создатели Nemerle говорят, что можно, но сложно.


Вы бы их не подкалывали по чем зря. А то они народ увлекающися. Ведь прикрутят с дури... вместо полноценного релиза.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Ошибочка в композиции
От: deniok Россия  
Дата: 11.01.07 10:39
Оценка: 6 (1)
Здравствуйте, deniok, Вы писали:

Кстати, здесь в коде нашёл ошибку в композиции

D>
D>data Eq a = MkEq (a->a->Bool) (a->a->Bool)
D>eq (MkEq e _) = e
D>ne (MkEq _ n) = n

D>dEqInt :: Eq Int
D>dEqInt =  MkEq eqInt (not . eqInt)
D>dEqList :: Eq a -> Eq [a]
D>dEqList (MkEq e _) =  MkEq el (not . el)
D>    where el []     []     = True
D>          el (x:xs) (y:ys) = x `e` y && xs `el` ys

D>member :: Eq a -> a -> [a] -> Bool
D>member d x [] = False
D>member d x (y:ys) | eq d x y  = True
D>                  | otherwise = member d x ys
D>


Функции eqInt и el — бинарные, и не могут обрабатываться оператором (.) (то есть комбинатором B). Тут надо либо так:
(curry (not . uncurry eqInt))

либо, как мы почти год назад обсуждали здесь
Автор: deniok
Дата: 19.04.06
, ввести оператор типа такого
-- f(g(x y))
(.##) :: (a -> b) -> (c -> d -> a) -> c -> d -> b
(.##) =  (.) . (.) 
-- можно было бы и так
-- (.##) f g = curry (f . uncurry g)
-- но это недостаточно point-free :)

и , используя его :
(not .## eqInt)


В книжке Душкина вычитал, что "мой" (.##) — это известный комбинатор B2.

Написал Худаку, он ответил, да, спасибо, This error was subsequently fixed in a later draft.
Re: [Haskell] dictionary-passing style
От: Аноним  
Дата: 06.02.07 14:54
Оценка:
Здравствуйте, deniok, Вы писали:

D>Собственно идея в том, что классы типов, несмотря на свою выразительную мощь, универсальным образом могут быть оттранслированы в обычные объявления типов. Я думаю, разработчики компиляторов приблизительно так и поступают.


разумеется. не святым же духом всё это работает

мы написали целую статью http://haskell.org/haskellwiki/OOP_vs_type_classes которая позволяет разобраться в чём разница между oop и type classes. в конце её есть ссылка на оригинальную статью Вадлера, где t.c. были впервые предложены и описан этот механизм их трансляции. я, пока эту статью не прочёл, тоже никак не мог грокнуть, почему t.c. не предоставляют некоторых возможностей OOP
Re[2]: [Haskell] dictionary-passing style
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 06.02.07 15:01
Оценка:
Здравствуйте, Аноним, Вы писали:

А>мы написали целую статью http://haskell.org/haskellwiki/OOP_vs_type_classes


Булат? А почему анонимом?
Re[2]: [Haskell] dictionary-passing style
От: Аноним  
Дата: 06.02.07 15:31
Оценка:
PM>Возможно, можно на макросах прикрутить к пресловутому Н. классы типов,
PM>но там перегрузка уже и так есть .

это разные вещи. в яве, C++, C# есть оба механизма. правда аналоги t.c. (шаблоны, generics) там только во время компиляции резолвятся. поэтому получается интересный винегрет:

классичсекие ООП языки — run-time связывание классов
java, C++, C# — run-time связывание классов ПЛЮС compile-time связывание аналогов t.c.
Haskell — run-time связывание t.c.


кроме того, хоть и не в самом хаскеле, но вокруг да около него есть такая штука как generic programming. в хаскеле под этим понимают автоматическую генерацию операций для типа по его структуре. ну к примеру если есть сложное AST, представляющее программу:

data AST = Var String
| Const Int
| .......
| Add AST AST
| While AST AST
| .......

то можно одной строчкой записать скажем альфа-переименование всех переменных в дереве, не заморачиваясь явным описанием обработки каждого варианта в AST. типа такого:

rename = gmap (\Var v -> Var ("v"++v))


так что это вообще чума, по сравнению с ООП при этом есть штук 7-10 реализаций с разными подходами, из них 3 (совершенно разноплановых) поставляются в комплекте с GHC
Re[3]: [Haskell] dictionary-passing style
От: Аноним  
Дата: 06.02.07 15:45
Оценка:
Здравствуйте, Аноним, Вы писали:

L>Булат? А почему анонимом?


ага. зарегистрируюсь потом

А>кроме того, хоть и не в самом хаскеле, но вокруг да около него есть такая штука как generic programming. в хаскеле под этим понимают автоматическую генерацию операций для типа по его структуре. ну к примеру если есть сложное AST, представляющее программу:


вопросы терминологии — самые коварные, и надо здесь сразу пояснить. то, что в с-подобных языках называют generic programming — т.е. использование templates/generics — это аналог хаскелловских t.c, кооторое в хаскеле называют полиморфизмом. скажем, эта функция:

sum :: Num a => [a] -> a

— полиморфная в терминологии Хаскелл и generic в терминологии наших ооп языков. а того, что в хаскеле называют generic программированием, у девочек вообще нет
Re[3]: [Haskell] dictionary-passing style
От: palm mute  
Дата: 06.02.07 16:27
Оценка:
Аноним wrote:
>
> PM>Возможно, можно на макросах прикрутить к пресловутому Н. классы типов,
> PM>но там перегрузка уже и так есть .
>
> это разные вещи. в яве, C++, C# есть оба механизма. правда аналоги t.c.
> (шаблоны, generics) там только во время компиляции резолвятся. поэтому
> получается интересный винегрет: ... <skipped>
>

Честно говоря, не понял суть возражения. Разве t.c. придуманы не для
того, чтобы сделать перегрузку (т.е. ad-hoc polymorphism) "less
add-hoc"?
Ваша статья очень интересная, но, ИМХО, есть пара моментов, требующих
уточнения.
1) При упоминании шаблонов C++, не указывается, какие шаблоны имеются в
виду — шаблоны классов или шаблоны функций
2) Как альтернативный классам типов механизм в C++ Вы называете классы и
шаблоны, тем временем поддержка в C++ перегруженных функций игнорируется

> то можно одной строчкой записать скажем альфа-переименование всех

> переменных в дереве, не заморачиваясь явным описанием обработки каждого
> варианта в AST. типа такого:
>
> rename = gmap (\Var v -> Var ("v"++v))
>
Scrap Your Boilerplate, если не ошибаюсь? Мощная штука, обязательно надо
разобраться.
Posted via RSDN NNTP Server 2.0
Re[4]: [Haskell] dictionary-passing style
От: Аноним  
Дата: 06.02.07 17:09
Оценка:
Здравствуйте, palm mute, Вы писали:

>> PM>Возможно, можно на макросах прикрутить к пресловутому Н. классы типов,

>> PM>но там перегрузка уже и так есть .
>>
>> это разные вещи. в яве, C++, C# есть оба механизма. правда аналоги t.c.
>> (шаблоны, generics) там только во время компиляции резолвятся. поэтому
>> получается интересный винегрет: ... <skipped>
>>

PM>Честно говоря, не понял суть возражения. Разве t.c. придуманы не для

PM>того, чтобы сделать перегрузку (т.е. ad-hoc polymorphism) "less
PM>add-hoc"?

это я ошибся, не сообразил что "перегрузка" — это overloading, а не классы редко приходится пользоваться русской терминологией

однако и здесь ситуация остаётся той же. overloading в C++ (не скажу за другие языки ) резолвится только во время компиляции. шаблоны функций — это вероятно просто способ описать кучу overloaded функций за раз?

в отличие от них, ТС позволяют передавать словари между методами во время исполнения, так что вышесказанное остаётся в силе

PM>Ваша статья очень интересная, но, ИМХО, есть пара моментов, требующих

PM>уточнения.
PM>1) При упоминании шаблонов C++, не указывается, какие шаблоны имеются в
PM>виду — шаблоны классов или шаблоны функций

функций. в общем-то, всё это — способы аватоматически найти нужное body функции из многих претендентов с одним и тем же именем

PM>2) Как альтернативный классам типов механизм в C++ Вы называете классы и

PM>шаблоны, тем временем поддержка в C++ перегруженных функций игнорируется

см. выше
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.