thedeemon: (Default)
[personal profile] thedeemon
Раз все молчат, напишу я. На днях вышел Окамл 4.0, в котором появились GADT. Рассказать о них можно на классическом примере: AST. Пусть у нас есть язычок с целыми числами, которые можно складывать и сравнивать. Результат сложения - целое число, результат сравнения - булев. Есть выражение if, в котором должно быть булево условие и пара выражений одинакового типа. Раньше, если мы все выражения этого язычка описали бы одним типом, то булевы и численные выражения оказались бы взаимозаменяемы, и проверку типов пришлось бы встраивать в интерпретатор, делать ее в рантайме. Если разнести численные и булевы выражения по разным типам, то можно было бы статически гарантировать, что условие в if'e точно будет булевым выражением, но пришлось бы делать разные функции для интерпретации и других операций над AST - как минимум одну для булевых выражений и одну для численных. С GADT можно все описать одним типом и одной функцией, не потеряв статических гарантий:
type 'a expr =  
  | I   : int -> int expr 
  | Add : int expr * int expr -> int expr 
  | Gt  : int expr * int expr -> bool expr 
  | If  : bool expr * 'a expr * 'a expr -> 'a expr;; 
 
let rec eval : type a . a expr -> a = function 
  | I n -> n 
  | Add(e1, e2) -> eval e1 + eval e2 
  | Gt(e1, e2)  -> eval e1 > eval e2 
  | If(cond, thn, els) -> if eval cond then eval thn else eval els;; 

Тип expr устроен таким образом, что в условии у if'a может оказаться только булево выражение, а складываться и сравниваться могут только численные. При этом функция eval для целочисленных выражений возвращает int, а для булевых - bool:
 
let e1 = If( Gt( Add(I 5, I 2), I 6 ),  I 1, I 0) in 
let b = Gt(I 1, I 2) in 
let e2 = If(b, b, b) in 
print_int (eval e1); 
Printf.printf " %b" (eval e2);; 

Результат вычисления выражения if имеет тот же тип, что оба подвыражения-ветки, причем их тип должен быть одинаковым, иначе компилятор не даст построить такое if-выражение.

А какие полезные применения GADT'ам знаете вы?

Date: 2012-07-30 11:57 am (UTC)
From: [identity profile] isorecursive.livejournal.com
В Hoopl их изящно используют - для описания открыто-закрытости форм потока управления:
http://hackage.haskell.org/packages/archive/hoopl/3.8.7.4/doc/html/src/Compiler-Hoopl-Graph.html#O

For example,
• A shift-left instruction is open on entry (because control can
fall into it from the preceding instruction), and open on exit
(because control falls through to the next instruction).
• An unconditional branch is open on entry, but closed on exit
(because control cannot fall through to the next instruction).
• A label is closed on entry (because in Hoopl we do not allow
control to fall through into a branch target), but open on exit.

http://research.microsoft.com/en-us/um/people/simonpj/papers/c--/dfopt.pdf
http://blog.ezyang.com/2011/04/hoopl-guided-tour-base-system/

Date: 2012-07-30 12:43 pm (UTC)
From: [identity profile] gds.livejournal.com
1.
> Раз все молчат

а что о них говорить? Они были доступны в окамле ещё очень давно, ценой "значения-свидетеля равенства типов". Вполне юзабельно. Я там даже как-то налепил расширение на случай подтипизации, правда на практике не пригодилось.

А у окамловских GADT'ов, судя по рассылке, всё непросто с выводом подтипизации на полиморфных вариантах. Тогда как в явном случае (со значением-свидетелем) можно оформить любую правильную схему подтипизации. По крайней мере, проблемы будут видны явно, при реализации, а не раскапыванием кода компилятора.

2. вот я как-то вбросил на тему того, что GADT'ы нужны только для "typed evaluator of dsl", до сих пор никто не опроверг. Кроме того, смесь интуиции и олеговских примеров показывает, что для dsl (и его typed evaluator) не обязательно GADT'ы, достаточно простых функций (код будет в стиле, слегка напоминающем cps, но его можно сделать максимально-"шитым", т.е. чтобы всё вычисление выражения, находящегося в dsl-типах, было оптимальным -- вызывался бы только нужный код -- только closures, ответственные за логику выполнения, а не за логику интерпретации).

3. в lwt в исходниках видел камент про то, что типы input/output channel "будут представлены GADT'ами, когда они будут в языке". Впрочем, деталей не помню, но этот камент, как подсказывает интуиция, всё ещё есть в исходниках lwt.

Date: 2012-07-30 12:49 pm (UTC)
From: [identity profile] dmzlj.livejournal.com
я как-то пытался на GADT типизировать опкоды, но не сумел. Пришлось в рантайме проверять.

Date: 2012-07-30 12:51 pm (UTC)
From: [identity profile] gds.livejournal.com
интересненько, а что требовалось от этой типизации?

Date: 2012-07-30 12:58 pm (UTC)
From: [identity profile] dmzlj.livejournal.com
Сейчас попробую сформулировать, давненько было.

Суть такова: есть у нас некие команды. У команды есть опкод, и есть операнды.

Опкод --- ну просто некие значения, идущие последовательно, без дырок.

Операнды --- ну, операнды разной разрядности, Word32 допустим. Или Word16. Или Word64. Или Label (которая потом заменяется на Word-соответствующей-разрядности-после-линковки)

С одной стороны, команды обрабатываются унифицировано. Ну, что там с ними делается --- допустим, asm pretty printing или генерация бинарных кодов.

С другой стороны, типы Команда Опкод Операнды надо бы задавать жёстко, что бы если уж определено, что инструкция имеет такой-то формат --- ни в каком другом формате ее нельзя бы было определить.


Что-то эта задача решалась только через
GADT + экзистенциальные типы + макро для генерации типов, причем GADT в таком случае особенно-то и не нужен оказался. Я так и не осилил это нормально типизировать как-то.


Date: 2012-07-30 01:05 pm (UTC)
From: [identity profile] migmit.livejournal.com
> вот я как-то вбросил на тему того, что GADT'ы нужны только для "typed evaluator of dsl", до сих пор никто не опроверг.

Ну, это зависит от понимания, что есть "typed evaluator of dsl", потому что при желании сюда можно притянуть за уши любой пример.

Вот, допустим, пишем мы библиотеку для работы со списками, но списками не конкретных типов - это Data.List, - а произвольных типов, поддерживающий заданный интерфейс. Такая библиотека будет содержать тип-обёртку:
data Wrapper c where Wrapper :: c a => a -> Wrapper c
type HeteroList c = [Wrapper c]

Без GADT-ов будет тяжело.

Date: 2012-07-30 01:12 pm (UTC)
From: [identity profile] gds.livejournal.com
а почему бы не сделать тупо? Выделить этот интерфейс в запись (или можно в объект, или в модульб если про окамл говорить), и складировать эти интерфейсы в список. Понятно, что решение тупое, но интересны его недостатки по сравнению с GADT'ами.

Date: 2012-07-30 01:24 pm (UTC)
From: [identity profile] migmit.livejournal.com
> Выделить этот интерфейс в запись (или можно в объект, или в модульб если про окамл говорить), и складировать эти интерфейсы в список.

Придётся для каждого класса делать отдельный тип записей, по сути дублируя существующий код. Соблюсти принцип DRY вполне можно, но... при помощи GADT-ов:
data Record c a where Record :: c a => Record c a

Date: 2012-07-30 01:52 pm (UTC)
From: [identity profile] nponeccop.livejournal.com
{-# LANGUAGE ExistentialQuantification #-}

data Obj = forall a. (Show a) => Obj a

xs :: [Obj]
xs = [Obj 1, Obj "foo", Obj 'c']

doShow :: [Obj] -> String
doShow [] = ""
doShow ((Obj x):xs) = show x ++ doShow xs

main = putStrLn $ doShow xs

В Окамле экзистеншиалы на функторах вроде как есть http://caml.inria.fr/pub/ml-archives/caml-list/2004/01/be82a912316130893f08204cbd86e0b5.en.html
Edited Date: 2012-07-30 01:54 pm (UTC)

Date: 2012-07-30 04:48 pm (UTC)
From: [identity profile] gds.livejournal.com
да, так. Только не обязательно функторы, в простом случае даже тупых лямбд хватает. Классический трюк с упаковыванием "2 forall 1 exists" остаётся в силе, но и без этого existential types есть. Остальное -- удобства, не обязательные, но желательные. И каждый дрочит как ему приятнее.

Date: 2012-07-30 04:45 pm (UTC)
From: [identity profile] gds.livejournal.com
вот, я тоже в сторону http://thedeemon.livejournal.com/51630.html?thread=655790#t655790 смотрел. Ну, в окамле будет чуть более явно: http://paste.in.ua/4565/

Date: 2012-07-30 05:59 pm (UTC)
From: [identity profile] migmit.livejournal.com
Ага, значит, экзистеншиалы за часть гадтов не считаем? Ладно, как насчёт типа Record выше? Это не просто экзистеншиал.

Date: 2012-07-30 06:27 pm (UTC)
From: [identity profile] gds.livejournal.com
> Ага, значит, экзистеншиалы за часть гадтов не считаем?

не считаем, так как для гадтов нужно только протягивание равенства между типами.

> Ладно, как насчёт типа Record выше? Это не просто экзистеншиал.

а вот тут, честно признаюсь, я не копенгаген. Начиная с того, что не совсем понимаю (хотя и есть догадки), что именно "data Record c a" постановляет. Наверное, Вам будет лень "возиться со мной", поэтому можем проехать этот вопрос.

Date: 2012-07-30 06:47 pm (UTC)
From: [identity profile] migmit.livejournal.com
Давайте вы не будете говорить за меня, ладно?

Постановляет только одно: наличие, собственно говоря, этого самого instance c a. То есть, если у вас есть какая-то функция вроде, скажем
f :: C a => Int -> (a -> String) -> Char -- от балды, по большей части

то вы можете соорудить функцию
f' :: Record C a -> Int -> (a -> String) -> Char
f' Record n h = f n h

при этом паттерн-матчинг по первому аргументу в левой части обеспечит вам право использовать instance C a в правой. Вариант
f' _ n h = f n h

не прокатит.

Date: 2012-07-30 09:43 pm (UTC)
From: [identity profile] migmit.livejournal.com
Собственно, можно и попроще. Общеизвестно, что список — монада, а вот Set — не монада, так как там на каждый чих нужен instance Ord для того, что в этом Set находится. С GADT-ами получается легко и приятно:
data Set' a where Set' :: Ord a => Set a -> Set' a
instance Monad Set' where
  return = Set' . singleton
  Set' sa >>= h = Set' $ fold (\a sb -> case h a of Set' sb' -> sb `union` sb') empty sa

если я не напутал нигде.

Date: 2012-07-30 06:33 pm (UTC)
From: [identity profile] nponeccop.livejournal.com
> Это не просто экзистеншиал.

Это и не GADT. Он требует ConstraintKinds, т.е. большинство реализаций GADT этого не позволяют.

Date: 2012-07-30 06:34 pm (UTC)
From: [identity profile] migmit.livejournal.com
Wrapper тоже требует.

Date: 2012-07-30 06:39 pm (UTC)
From: [identity profile] nponeccop.livejournal.com
Ох, я не заметил. Оффтопик: а как значения Record конструировать? Параметра у конструктора нет, как у меня ниже:

data Record c a where Record :: c a => a -> Record c a

Date: 2012-07-30 06:48 pm (UTC)
From: [identity profile] migmit.livejournal.com
Не понял, в чём проблема с конструированием. Конструктор-то есть.

Date: 2012-07-30 07:12 pm (UTC)
From: [identity profile] nponeccop.livejournal.com
Он без параметров

Date: 2012-07-30 07:15 pm (UTC)
From: [identity profile] migmit.livejournal.com
Э-э-э... и? Мы конструкторов без параметров не видали, что ли? data Bool = False | True, например.

Date: 2012-07-30 07:25 pm (UTC)
From: [identity profile] nponeccop.livejournal.com
Так у Bool паттерн-матчинг позволяет извлекать полезную информацию. А здесь из полезной информации - голый словарь.

Как его использовать для соблюдения DRY в примере?

Date: 2012-07-30 07:41 pm (UTC)
From: [identity profile] migmit.livejournal.com
> А здесь из полезной информации - голый словарь.

Ничего себе "голый словарь". Это ж дофига информации!

> Как его использовать для соблюдения DRY в примере?

В примере — не слишком интересно. data Wrapper' c where Wrapper' :: Record c a -> a -> Wrapper' c, те же яйца, вид в профиль.

Куда интереснее использовать его для других целей. Например — как подсказать компилятору, что один класс есть подкласс другого? (Ну да, теоретически, компилятор и сам должен это выводить, но увы, не умеет)

А вот как:
data Void (c :: * -> Constraint) = Void -- это чисто для удобства
class c1 :<= c2 where
  isSubClass :: c1 a => Void c1 -> Record c2 a

Можно даже упростить создание инстансов, добавив в этот класс такое:
  default isSubClass :: c2 a => Void c1 -> Record c2 a
  isSubClass _ = Record

После этого, если есть какой-то
class Show a => Shit a where -- ну, там, какие-то методы

достаточно будет объявить
instance Shit :<= Show

и компилятор удовлетворится. А вот instance Show :<= Shit, или instance Num :<= Show не скомпилируются.

Зачем это нужно? Ну, например, можно будет писать такое:
instance (c :<= Show) => Show (Wrapper c) where
  show (Wrapper (x :: a)) = case isSubClass (Void :: Void c) :: Record Show a of Record -> show x

Без этого фокуса придётся отдельно делать
instance Show (Wrapper Show) where show (Wrapper x) = show x
instance Show (Wrapper Shit) where show (Wrapper x) = show x

и те де.
Edited Date: 2012-07-30 07:42 pm (UTC)

Date: 2012-07-30 01:30 pm (UTC)
From: [identity profile] nponeccop.livejournal.com
Ну гадты - это тяжелая артилерия, для этого экзистеншиалов хватило бы, которые можно сделать без гадтов

Date: 2012-07-30 01:39 pm (UTC)
From: [identity profile] nponeccop.livejournal.com
> GADT'ы нужны только для "typed evaluator of dsl"

Ну, GADTs are poor man's dependent types - на них произвольные статические гарантии можно записывать, вроде тотальности head и сбалансированности деревьев (cтр. 87)

Тот же HOOPL уже упоминался - я использую его в HNC, удобно.

См. тж.

http://lambda-the-ultimate.org/node/1293
http://www.cs.st-andrews.ac.uk/~eb/drafts/tfp08.pdf
Edited Date: 2012-07-30 01:45 pm (UTC)

Date: 2012-07-30 04:11 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Ok.
А в overbld 4.0 стоит ожидать? А то я какой-то виндовый инсталлятор по ссылке из рассылки нашел, он в байткод нормально компилит, а с ocamlopt опять все непросто.

Date: 2012-07-30 04:20 pm (UTC)
From: [identity profile] gds.livejournal.com
в целом -- да, overbld будет обязательно. Самое полное мнение по этому вопросу я изложил в калочятике: http://chatlogs.jabber.ru/ocaml@conference.jabber.ru/2012/07/27.html#11:54:04.790788

Date: 2012-07-30 07:02 pm (UTC)
From: [identity profile] stdray.livejournal.com
А разве не через них реализуются всякие option[T] и either[T,U]? Если, конечно, я правильно все понял.

Date: 2012-07-30 07:47 pm (UTC)
From: [identity profile] triampurum.livejournal.com
вот замечательное определение: http://cstheory.stackexchange.com/questions/10594/whats-the-difference-between-adts-gadts-and-inductive-types/10596#10596
Замечательно двумя вещами: ясностью и полной неожиданностью для многих хаскелистов.

Date: 2012-07-30 07:49 pm (UTC)
From: [identity profile] triampurum.livejournal.com
(и считаем комментарии "это определение неверно")

Date: 2012-07-30 07:56 pm (UTC)
From: [identity profile] migmit.livejournal.com
Можно, я буду первым? Это определение — хуйня. jbapple там правильно сказал.

Date: 2012-07-31 02:27 am (UTC)
From: [identity profile] thedeemon.livejournal.com
То просто ADT.

Profile

thedeemon: (Default)
Dmitry Popov

February 2026

S M T W T F S
12 34567
891011121314
15161718192021
22232425262728

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Mar. 7th, 2026 10:08 pm
Powered by Dreamwidth Studios