1

環境

IBM の OLP (線形プログラミングのモデリング言語) に大まかに似た EDSL を実装しようとしています。

コード

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]]]
...
4

2 に答える 2

2

によって実際に格納される値を知るにはIterate n [] Double、 に関する情報を知っておく必要がありますn。この情報は通常、GADT のインデックスによって与えられます。これは、インデックス自体の誘導構造 (一般にsingletonとして知られています) に対応します。

しかし幸いなことに、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(単純に定義による)。2 番目のパターンでは、n ~ k + 1for somekとがあり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、インデックスの実際の値にアクセスするために追加の作業を行う必要はありません。ベクターでパターン マッチするだけです。

于 2016-04-24T15:13:39.297 に答える