Проблемы с реализацией подписки на массив в Haskell, связанной с EDSL

Контекст

Я пытаюсь реализовать EDSL, который немного напоминает IBM OLP (язык моделирования для линейного программирования).

Код

Код Haskell EDSL

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}

-- Numbers at the type level
data Peano = Zero | Successor Peano

-- Counting Vector Type. Type information contains current length
data Vector peanoNum someType where
    Nil :: Vector Zero someType
    (:+) :: someType 
            -> Vector num someType 
            -> Vector (Successor num) someType
infixr 5 :+ 

-- Generate Num-th nested types
-- For example: Iterate (S (S Z)) [] Double => [[Double]]
type family Iterate peanoNum constructor someType where
    Iterate Zero cons typ = typ
    Iterate (Successor pn) cons typ = 
        cons (Iterate pn cons typ)

-- DSL spec

data Statement =
      DecisionVector [Double]
    | Minimize Statement
    | Iteration `Sum` Expression
    | Forall Iteration Statement
    | Statement :| Statement
    | Constraints Statement
infixl 8 `Sum`
infixl 3 :|

data Iteration =
      String `In` [Double]
    | String `Ins` [String]

data Expression where
    EString :: String -> Expression
    EFloat :: Double -> Expression
    (:?) :: Vector n Expression -> Iterate (n) [] Double -> Expression
    (:*) :: Expression -> Expression -> Expression
    Lt :: Expression -> Expression -> Expression
    Gt :: Expression -> Expression -> Expression
    Id :: String -> Expression
infixr 5 `Lt`
infixr 5 `Gt`
infixr 6 :*
infixr 7 :?

test :: Statement
test = 
    let rawMaterial = 205
        products = ["light", "medium", "heavy"]
        demand = [59, 12, 13]
        processes = [1, 2] 
        production = [[12,16], [1,7], [4,2]]
        consumption = [25, 30]
        -- foo = (EId "p" :+ EId "f" :+ Nil) `Subscript` production
        -- bar = (EId "p" :+ Nil) `Subscript` cost
        run = []
        cost = [300, 400]
    in  
        DecisionVector run :|
        Minimize 
            (Sum ("p" `In` processes) 
                 ((Id "p" :+ Nil) :? cost :*
                  (Id "p" :+ Nil) :? run)) :|
        Constraints 
            (Sum ("p" `In` processes)
                 ((Id "p" :+ Nil) :? consumption :*
                  (Id "p" :+ Nil) :? run `Lt` EFloat rawMaterial) :|
             Forall ("q" `Ins` products)
                    (Sum ("p" `In` processes)
                         ((Id "q" :+ Id "p" :+ Nil) :? production :*
                          (Id "p" :+ Nil) :? run `Gt` 
                          (Id "q" :+ Nil) :? demand)))

instance Show Statement where
    show (DecisionVector v) = show v
    show (Minimize s) = "(Minimize " ++ show s ++ ")"
    show (i `Sum` e) = "(" ++ show i ++ " `Sum` " ++ show e ++ ")"
    show (Forall i e) = "(Forall " ++ show i ++ show e ++ ")"
    show (sa :| sb) = "(" ++ show sa ++ show sb ++ ")"
    show (Constraints s) = "(Constraints " ++ show s  ++ ")"

instance Show Iteration where
    show (str `In` d) = "(" ++ show str ++ " `In` " ++ show d ++ ")"
    show (str `Ins` d) = "(" ++ show str ++ " `Ins` " ++ show d ++ ")"

instance Show Expression where
    show (EString s) = "(EString " ++ show s ++ ")"
    show (EFloat f) = "(EFloat " ++ show f ++ ")"
    show (Lt ea eb) = "(" ++ show ea ++ " `Lt` " ++ show eb ++ ")"
    show (Gt ea eb) = "(" ++ show ea ++ " `Gt` " ++ show eb ++ ")"
    show (ea :* eb) = "(" ++ show ea ++ " :* " ++ show eb ++ ")"
    show (Id s) = "(Id " ++ show s ++ ")"
    show (vec :? dbl) = "(" ++ show vec ++ " :? " ++ "dbl" ++ ")"

instance Show (Vector p Expression) where
    show (Nil) = "Nil"
    show (e :+ v) = "(" ++ show e ++ " :+ " ++ show v ++ ")"

-- eval_opl :: Statement -> [Double]

Сравнение EDSL и OPL

    let rawMaterial = 205
        products = ["light", "medium", "heavy"]
        demand = [59, 12, 13]
        processes = [1, 2] 
        production = [[12,16], [1,7], [4,2]]
        consumption = [25, 30]
        -- foo = (EId "p" :+ EId "f" :+ Nil) `Subscript` production
        -- bar = (EId "p" :+ Nil) `Subscript` cost
        run = []
        cost = [300, 400]
    in  
        DecisionVector run :|
        Minimize 
            (Sum ("p" `In` processes) 
                 ((Id "p" :+ Nil) :? cost :*
                  (Id "p" :+ Nil) :? run)) :|
        Constraints 
            (Sum ("p" `In` processes)
                 ((Id "p" :+ Nil) :? consumption :*
                  (Id "p" :+ Nil) :? run `Lt` EFloat rawMaterial) :|
             Forall ("q" `Ins` products)
                    (Sum ("p" `In` processes)
                         ((Id "q" :+ Id "p" :+ Nil) :? production :*
                          (Id "p" :+ Nil) :? run `Gt` 
                          (Id "q" :+ Nil) :? demand)))

соответствует коду opl

float rawMaterial                     = 205;
{string} products                     = {"light","medium","heavy"};
float demand[products]                = [59,12,13];
{string} processes                    = {"1","2"};
float production[products][processes] = [[12,16],[1,7],[4,2]];
float consumption[processes]          = [25,30];
float cost[processes]                 = [300,400];

dvar float+ run[processes];

minimize sum (p in processes) cost[p] * run[p];

constraints {
  sum (p in processes) consumption[p] * run[p] <= rawMaterial;
  forall (q in products)
    sum (p in processes) production[q][p] * run[p] >= demand[q];
}

Соответствующие разделы

(:?) :: Vector n Expression -> Iterate (n) [] Double -> Expression

так же как

instance Show Expression where
    [...]
    show (vec :? dbl) = "(" ++ show vec ++ " :? " ++ "dbl" ++ ")"

Описание проблемы

OPL использует скобки для подписки на массив, и я попытался сопоставить подписки с моим EDSL, используя следующую запись

((Id "p" :+ Id "f" :+ Nil) :? consumption

что соответствует OPL в следующем смысле:

consumption[p][f]

в первом случае (Id "p" :+ Id "f" :+ Nil) создает значение типа Vector, которое содержит информацию уровня типа относительно длины указанного вектора. Согласно определению конструктора :?, вы можете видеть, что Iterate (n) [] Double, таким образом, расширится до [[Double]]. Это аккуратно работает, как и ожидалось. Однако, в свою очередь, чтобы использовать сгенерированное синтаксическое дерево, мне нужно сопоставить шаблон с фактическими значениями.

show (vec :? dbl) = "(" ++ show vec ++ " :? " ++ "dbl" ++ ")"

Проблема: приведенная выше строка работает, но я не знаю, как использовать фактические данные. Как сопоставить шаблон? Можно ли вообще использовать данные? Замена dbl через очевидное

(Iterate (Successor (Successor Zero)) [] Double)

не работает. Я также пытался создать семейство данных, но не смог найти способ рекурсивно создать семейство всех произвольно вложенных списков Double:

Double
[Double]
[[Double]]
[[[Double]]]
...

person forflo    schedule 23.04.2016    source источник
comment
Я не знаю, как использовать фактические данные — использовать каким образом? Ваш вопрос загроможден лишними деталями, но не содержит четкого объяснения того, что вы на самом деле хотите. Вам подходит это?   -  person user3237465    schedule 24.04.2016


Ответы (2)


Чтобы узнать, какое значение на самом деле хранится в Iterate n [] Double, вы должны знать некоторую информацию о n. Эта информация обычно предоставляется индексами некоторых GADT, которые соответствуют индуктивной структуре самого индекса (широко известного как одиночка).

Но, к счастью для вас, вы уже сохранили индекс Nat в структуре файла Vector. У вас уже есть вся необходимая информация под рукой, вам просто нужно сопоставить шаблон! например

instance Show Expression where
    ...
    show (vec :? dbl) = "(" ++ show vec ++ go vec dbl ++ ")" where 
      go :: Vector n x -> Iterate n [] Double -> String 
      go Nil a = show a 
      go (_ :+ n) a = "[" ++ intercalate "," (map (go n) a) ++ "]" 

Обратите внимание, что в первом шаблоне тип Nil дает вам n ~ 0, который, в свою очередь, дает вам Iterate 0 [] Double ~ Double (просто по определению). Во втором шаблоне у вас есть n ~ k + 1 для некоторых k и Iterate n [] Double ~ [ Iterate k [] Double ]. Сопоставление с образцом на Nat позволяет вам, по сути, увидеть индуктивную структуру семейства типов.

Каждая функция, которую вы пишете на Iterate, будет выглядеть так:

foo :: forall n . Vector n () -> Iterate n F X -> Y  -- for some X,Y

потому что у вас должно быть такое доказательство на уровне значений, чтобы написать любую индуктивную функцию на Iterate. Если вам не нравится носить с собой эти «фиктивные» значения, вы можете сделать их неявными с помощью класса:

class KnownNat n where 
  isNat :: Vector n () 

instance KnownNat 'Z where isNat = Nil 
instance KnownNat n => KnownNat ('S n) where isNat = () :+ isNat

но в этом случае, поскольку ваш AST уже содержит конкретный Vector, вам не нужно выполнять какую-либо дополнительную работу для доступа к фактическому значению индекса — достаточно сопоставления шаблона в векторе.

person user2407038    schedule 24.04.2016
comment
Это именно то, что я искал. Я очень ценю это! - person forflo; 25.04.2016

У вас есть несколько вариантов, все они сводятся к кодированию глубины итерации на уровне значений, чтобы вы могли сопоставлять их с образцом.

ГАДТ

Самый простой способ сделать это — создать GADT для представления итерации приложения конструктора типа:

data IterateF peanoNum f a where
    ZeroF      :: a                   -> IterateF Zero           f a
    SuccessorF :: f (IterateF pn f a) -> IterateF (Successor pn) f a

instance Functor f => Functor (IterateF peanoNum f) where
    fmap f (ZeroF a)       = ZeroF $ f a
    fmap f (SuccessorF xs) = SuccessorF $ fmap (fmap f) xs

-- There's also an Applicative instance, see Data.Functor.Compose

Синглтон

Если вы привязаны к своему семейству типов, вместо этого вы можете использовать синглтон. Синглтон — это тип, содержащий одно значение, с которым вы можете сопоставить шаблон, чтобы представить компилятору факты, известные об этом типе. Ниже приведен синглтон для натуральных чисел:

{-# LANGUAGE FlexibleContexts #-}

data SPeano pn where
    SZero :: SPeano Zero
    SSuccessor :: Singleton (SPeano pn) => SPeano pn -> SPeano (Successor pn)

class Singleton a where
    singleton :: a

instance Singleton (SPeano Zero) where
    singleton = SZero

instance Singleton (SPeano s) => Singleton (SPeano (Successor s)) where
    singleton = SSuccessor singleton

Эквивалентен более простой синглтон SPeano без класса типа Singleton, но эта версия не требует написания такого количества доказательств, вместо этого она фиксирует их при построении преемника.

Если мы изменим IterateF GADT из предыдущего раздела, чтобы получить те же доказательства (потому что я ленив), мы сможем преобразовать GADT всякий раз, когда у нас есть SPeano синглтон. Несмотря ни на что, мы можем легко преобразовать GADT.

data IterateF peanoNum f a where
    ZeroF      ::                          a                   -> IterateF Zero           f a
    SuccessorF :: Singleton (SPeano pn) => f (IterateF pn f a) -> IterateF (Successor pn) f a

toIterateF :: Functor f => SPeano pn -> Iterate pn f a -> IterateF pn f a
toIterateF SZero a = ZeroF a
toIterateF (SSuccessor pn) xs = SuccessorF $ fmap (toIterateF pn) xs

getIterateF :: Functor f => IterateF pn f a -> Iterate pn f a
getIterateF (ZeroF a) = a
getIterateF (SuccessorF xs) = fmap getIterateF xs

Теперь мы можем легко создать альтернативное представление для IterateF, которое представляет собой синглтон и приложение семейства типов Iterate.

data Iterated pn f a = Iterated (SPeano pn) (Iterate pn f a)

Я ленив и не люблю писать доказательства, которые мог бы обработать за меня GADT, поэтому я просто оставлю IterateF GADT и напишу функции для Iterated в его терминах.

toIterated :: Functor f => IterateF pn f a -> Iterated pn f a
toIterated xs@(ZeroF      _) = Iterated singleton (getIterateF xs)
toIterated xs@(SuccessorF _) = Iterated singleton (getIterateF xs)

fromIterated :: Functor f => Iterated pn f a -> IterateF pn f a
fromIterated (Iterated pn xs) = toIterateF pn xs

instance Functor f => Functor (Iterated pn f) where
    fmap f = toIterated . fmap f . fromIterated

Сопоставление с образцом в toIterated должно представить доказательства, захваченные в построении SuccessorF. Если бы нам нужно было сделать что-то более сложное, мы могли бы записать это в Dict

Используйте любую другую кодировку

В конкретном случае

(:?) :: Vector n Expression -> Iterate (n) [] Double -> Expression

у вас есть Vector n, который кодирует глубину итерации Iterate n [] на уровне значения. Вы можете сопоставить шаблон вектора, который равен либо Nil, либо (_ :+ xs), чтобы доказать, что Iterate n [] является либо Double, либо списком. Вы можете использовать это для простых случаев, таких как showвложенных значений, или вы можете преобразовать Vector n в другой синглтон для используйте одно из более мощных представлений из предыдущих разделов.

-- The only proof we need to write by hand
ssuccessor :: SPeano pn -> (SPeano (Successor pn))
ssuccessor pred =
    case pred of
        SZero        -> SSuccessor pred
        SSuccessor _ -> SSuccessor pred

lengthSPeano :: Vector pn st -> SPeano pn
lengthSPeano Nil = SZero
lengthSPeano (_ :+ xs) = ssuccessor (lengthSPeano xs)
person Cirdec    schedule 24.04.2016
comment
Но это не решает мою проблему. Теперь мне пришлось бы писать что-то вроде SuccessorF (Successor F (ZeroF 32)) для [[32]] в моем EDLS, что совершенно неприемлемо. - person forflo; 24.04.2016
comment
все они сводятся к кодированию глубины итерации на уровне значений — она уже закодирована в типе данных Vector. Для этого не нужно определять какие-либо дополнительные механизмы. - person user2407038; 24.04.2016