Использование функторов для глобальных переменных? - PullRequest
2 голосов
/ 25 февраля 2011

Я изучаю Haskell и реализую алгоритм для класса. Это работает хорошо, но требование класса состоит в том, чтобы я вел подсчет общего количества раз, которое я умножил или добавил два числа. Это то, для чего я бы использовал глобальную переменную в других языках, и я понимаю, что это анафема для Хаскелла.

Один вариант - просто заставить каждую функцию возвращать эти данные вместе с их фактическим результатом. Но это не кажется забавным.

Вот то, о чем я думал: предположим, у меня есть какая-то функция f :: Double -> Double. Могу ли я создать тип данных (Double, IO), а затем использовать функтор для определения умножения на (Double, IO), чтобы выполнить умножение и записать что-то в IO. Тогда я мог бы передать мои новые данные в мои функции просто отлично.

Имеет ли это какой-то смысл? Есть ли более простой способ сделать это?

РЕДАКТИРОВАТЬ: Чтобы быть более понятным, на языке ОО я бы объявил класс, который наследуется от Double, а затем переопределить операцию *. Это позволило бы мне не переписывать сигнатуру типа моих функций. Мне интересно, есть ли способ сделать это в Хаскеле.

В частности, если я определю f :: Double -> Double, тогда я смогу сделать functor :: (Double -> Double) -> (DoubleM -> DoubleM) правильно? Тогда я могу сохранить свои функции такими же, как сейчас.

Ответы [ 4 ]

5 голосов
/ 25 февраля 2011

На самом деле, ваша первая идея (возвращать счетчики с каждым значением) неплоха и может быть выражена более абстрактно монадой Writer (в Control.Monad.Writer из пакета mtl или Control.Monad.Trans.Writer из пакета преобразователей),По сути, монада-писатель позволяет каждому вычислению иметь связанный «вывод», который может быть любым, если он является экземпляром Monoid - класса, который определяет:

  • Пустой вывод (mempty), который является выходом, назначенным для 'return'
  • Ассоциативная функция (`mappend '), которая объединяет выходные данные, которая используется при операциях секвенирования

В этом случаеваш вывод - это число операций, «пустое» значение равно нулю, а операция объединения - это сложение.Например, если вы отслеживаете операции по отдельности:

data Counts = Counts { additions: Int, multiplications: Int }

Создайте для этого типа экземпляр Monoid (который находится в модуле Data.Monoid) и определите ваши операции как-то вроде:

add :: Num a => a -> a -> Writer Counts a
add x y = do
    tell (Counts {additions = 1, multiplications = 0})
    return (x + y)

Монада-писатель вместе с экземпляром Monoid позаботится о распространении всех «рассказов» на верхний уровень.Если вы хотите, вы можете даже реализовать экземпляр Num для Num a => Writer Counts a (или, предпочтительно, для нового типа, чтобы не создавать экземпляр-сироту), чтобы вы могли просто использовать обычные числовые операторы.

4 голосов
/ 25 февраля 2011

Вот пример использования Writer для этой цели:

import Control.Monad.Writer
import Data.Monoid
import Control.Applicative -- only for the <$> spelling of fmap

type OpCountM = Writer (Sum Int)

add :: (Num a) => a -> a -> OpCountM a
add x y = tell (Sum 1) >> return (x+y)

mul :: (Num a) => a -> a -> OpCountM a
mul x y = tell (Sum 1) >> return (x*y)

-- and a computation
fib :: Int -> OpCountM Int
fib 0 = return 0
fib 1 = return 1
fib n = do
    n1 <- add n (-1)
    n2 <- add n (-2)
    fibn1 <- fib n1
    fibn2 <- fib n2
    add fibn1 fibn2

main = print (result, opcount)
  where
  (result, opcount) = runWriter (fib 10)

Это определение fib довольно длинное и уродливое ... монодификация может быть болезненнойЭто может быть сделано более кратким с аппликативной нотацией:

fib 0 = return 0
fib 1 = return 1
fib n = join (fib <$> add n (-1) <*> add n (-2))

Но по общему признанию более непрозрачным для новичка.Я бы не советовал так, пока вы не освоитесь с идиомами Хаскелла.

1 голос
/ 25 февраля 2011

Другое решение, помимо возврата кортежа или явного использования монады состояния, может заключаться в том, чтобы обернуть его в тип данных. Что-то вроде:

data OperationCountNum = OperationCountNum Int Double deriving (Show,Eq)

instance Num OperationCountNum where
    ...insert appropriate definitions here

Класс Num определяет функции для чисел, поэтому вы можете определять функции +, * и т. Д. Для вашего типа OperationCountNum таким образом, чтобы они отслеживали количество операций, необходимых для получения каждого числа.

Таким образом, подсчет операций будет скрыт, и вы сможете использовать обычные операции +, * и т. Д. Вам просто нужно обернуть свои числа в типе OperationCountNum в начале, а затем извлечь их в конце.

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

1 голос
/ 25 февраля 2011

Какой уровень Haskell вы изучаете? Вероятно, есть два разумных ответа: пусть каждая функция возвращает свои счетчики вместе с возвращаемым значением, как вы предложили, или (более продвинутый) использует монаду, такую ​​как State, чтобы сохранить счет в фоновом режиме. Вы также можете написать монаду специального назначения, чтобы вести счет; Я не знаю, хотел ли это ваш профессор. Использование IO для изменяемых переменных не является элегантным способом решения проблемы и не является необходимым для того, что вам нужно.

...