Использование ViewPatterns и PatternSynonyms для простого сопоставления с образцом

Допустим, у меня есть GADT для такого языка (мой настоящий язык гораздо более сложный, около 50 конструкторов, но это упрощенный пример):

data Expr t where
  Add :: Expr t -> Expr t -> Expr t
  Sub :: Expr t -> Expr t -> Expr t
  Mult :: Expr t -> Expr t -> Expr t
  Negate :: Expr t -> Expr t
  Abs :: Expr t -> Expr t
  Scalar :: t -> Expr t

Теперь давайте определим другой тип данных следующим образом:

data BinOpT = AddOp | SubOp | MultOp

Также, допустим, у меня есть следующая функция:

stringBinOp :: BinOpT -> String
stringBinOp AddOp = "+"
stringBinOp SubOp = "-"
stringBinOp MultOp = "*"

Также давайте определим следующий тип:

data BinOp t = BinOp BinOpT (Expr t) (Expr t)

Теперь я хочу определить красивую функцию печати следующим образом:

prettyPrint :: Show t => Expr t -> String
prettyPrint (BinOp op x y) = prettyPrint x ++ showOp op ++ prettyPrint y
prettyPrint (Negate x) = "-" ++ prettyPrint x
prettyPrint (Abs x) = "abs(" ++ prettyPrint x ++ ")"
prettyPrint (Scalar x) = show x

Обратите внимание, что это недействительно, так как BinOp не является конструктором Expr t,

Конечно, я мог бы переопределить Expr t вот так:

data Expr t where
  BinOp :: BinOp -> Expr t -> Expr t -> Expr t
  Negate :: Expr t -> Expr t
  Abs :: Expr t -> Expr t
  Scalar :: t -> Expr t

И это будет работать нормально, но я бы предпочел не делать этого. Это делает другой код, который использует это, немного уродливее, а также я думаю, что он будет немного более неэффективным с точки зрения пространства и времени, и вам придется сопоставлять два конструктора вместо одного, что означает два оператора case (следовательно, jump таблицы) вместо одного.

Я подозреваю, что могу использовать комбинацию следующих двух расширений GHC, чтобы добиться того, что я пытаюсь сделать чисто, а именно:

{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE PatternSynonyms #-}

Но я не совсем уверен, как лучше всего это сделать. Был бы полезен простой пример для этого кода (затем я могу применить его к более сложному языку, с которым я имею дело).

Многие мнимые бонусные баллы будут начислены, если решение будет скомпилировано без предупреждений о пропущенных совпадениях. Я понимаю, что GHC 8.2 может быть полезен в этом отношении, поэтому пример GHC 8.2 с его расширениями для проверки исчерпываемости будет хорошим, хотя решение до GHC 8.2 с прохождением проверки исчерпанности будет еще лучше.

Разъяснение:

На самом деле я спрашиваю, как я могу сделать что-то вроде этого:

prettyPrint :: Show t => Expr t -> String
prettyPrint (BinOp op x y) = prettyPrint x ++ showOp op ++ prettyPrint y
prettyPrint (Negate x) = "-" ++ prettyPrint x
prettyPrint (Abs x) = "abs(" ++ prettyPrint x ++ ")"
prettyPrint (Scalar x) = show x

Сохраняя определение Expr t вот так:

data Expr t where
  Add :: Expr t -> Expr t -> Expr t
  Sub :: Expr t -> Expr t -> Expr t
  Mult :: Expr t -> Expr t -> Expr t
  Negate :: Expr t -> Expr t
  Abs :: Expr t -> Expr t
  Scalar :: t -> Expr t

Важная строка:

prettyPrint (BinOp op x y) = prettyPrint x ++ showOp op ++ prettyPrint y

Который не будет компилироваться как BinOp не является конструктором Expr t, Я хочу, чтобы эта строка компилировалась, поскольку я не хочу делать это везде:

prettyPrint (Add x y) = ...
prettyPrint (Sub x y) = ...
prettyPrint (Mult x y) = ...

Потому что это означает много дублирования кода, так как многие функции будут использовать Expr t,

1 ответ

Решение

Посмотреть шаблон

asBinOp (Add a b) = Just (AddOp, a, b)
asBinOp (Sub a b) = Just (SubOp, a, b)
asBinOp (Mul a b) = Just (MulOp, a, b)
asBinOp _ = Nothing

prettyPrint (asBinOp -> Just (op, x, y)) = prettyPrint x ++ showOp op ++ prettyPrint y

... + Паттерн синоним

pattern BinOp :: BinOpT -> Expr t -> Expr t -> Expr t
pattern BinOp op a b <- (asBinOp -> Just (op, a, b)) where
  BinOp AddOp a b = Add a b
  BinOp SubOp a b = Sub a b
  BinOp MulOp a b = Mul a b

prettyPrint (BinOp op x y) = prettyPrint x ++ showOp op ++ prettyPrint y

В GHC 8.2 вы можете удовлетворить проверку исчерпанности с помощью этой прагмы:

{-# COMPLETE BinOp, Negate, Abs, Scalar #-}
Другие вопросы по тегам