Идиоматический способ выразить общие вычисления в Haskell
Должен существовать хороший идиоматический способ выражения общих вычислений в Haskell на уровне типов. Все, что я могу придумать, - это (незаконная) ОО имитация.
class Computation where
compute :: Computation -> Double -> Double
data Id = Id
instance Computation Id where
compute _ = id
data Square a = Computation a => Square a
instance Computation (Square a) where
compute (Square underlying) x = sqr $ compute underlying x where square x = x*x
data Scale a = Computation a => Scale a Double
compute (Scale underlying c) x = c * compute underlying x
В идеале я хотел бы сохранить открытость, поэтому такой подход мне не подходит. Я спрашиваю слишком много?
2 ответа
Вы, конечно, можете сделать это с подходом, который вам нужен, вам просто нужно правильно понять синтаксис и некоторые детали, но это, безусловно, работает:
class Computation a where
compute :: a -> Double
instance Computation Double where
compute = id
data Square a = Square a
instance Computation a => Computation (Square a) where
compute (Square underlying) = square $ compute underlying where square i = i * i
data Scale a = Scale a Double
instance Computation a => Computation (Scale a) where
compute (Scale underlying c) = c * compute underlying
data Add a = Add a Double
instance Computation a => Computation (Add a) where
compute (Add underlying c) = c + compute underlying
test :: Add (Scale (Scale (Square Double)))
test = Add (Scale (Scale (Square 2) 5) 0.5) 100
main :: IO ()
main = print $ compute test
Обратите внимание, что мне пришлось добавить экземпляр Computation
за Double
что просто const
, test
выражение должно быть эквивалентно (((2^2) * 5) * 0.5) + 100
И, действительно, сравнивая эти два результата, я получаю одинаковое значение.
Я не совсем уверен, что вы хотели именно такой подход. Это также на самом деле не эквивалентно методу, показанному в опубликованной вами ссылке, в этой кодировке было бы довольно сложно выразить переменные, поскольку нет хорошего способа ввести в карту все значения переменных, чтобы уменьшить выражение.
Это зависит от того, что вы хотите сделать с вычислениями, но один идиоматический способ заключается в следующем:
data Computation = Computation { compute :: Double -> Double }
Тогда вы можете иметь:
idCmp :: Computation
idCmp = Computation id
squareCmp :: Computation
squareCmp = Computation (\i -> i * i)
composeCmp :: Computation -> Computation -> Computation
composeCmp b a = Computation (compute b . compute a)
scaleCmp :: Double -> Computation
scaleCmp r = Computation (r*)
и т.д. Вы могли бы назвать это своего рода "комбинаторами вычислений".