Идиоматический способ выразить общие вычисления в 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*)

и т.д. Вы могли бы назвать это своего рода "комбинаторами вычислений".

Другие вопросы по тегам