Я читаю Программирование Грэма Хаттона в Haskell, и меня смущает поток мыслей, изложенный ниже.
Он использует приведенный ниже пример, чтобы мотивировать использование монад, показывая недостатки аппликативных функторов для Операция деления, где тип возвращаемого значения - Maybe
для обработки случая ошибки, указывающего потенциальный сценарий деление на ноль .
Дано:
data Expr = Val Int | Div Expr Expr
safediv :: Int -> Int -> Maybe Int
safediv _ 0 = Nothing
safediv n m = Just (n `div` m)
eval :: Expr -> Maybe Int
eval (Val n) = pure n --type: Just(n)?
eval (Div x y) = pure safediv <*> eval x <*> eval y --type: Maybe(Maybe Int)?
Он идет объяснить:
Однако это определение не является корректным типом. В частности, функция safediv
имеет тип Int->Int->Maybe Int
, тогда как в вышеприведенном контексте требуется функция типа Int->Int->Int
.
Замена pure safediv
пользовательской функцией намотки тоже не поможет, поскольку эта функция должна иметь тип Maybe(Int->Int->Int)
, который не предоставляет никаких средств для указания сбоя, когда второй целочисленный аргумент равен нулю. (X)
Вывод состоит в том, что функция eval
не соответствует шаблону эффективного программирования, который фиксируется аппликативными функторами. Аппликативный стиль ограничивает нас применением чистых функций к эффективным аргументам: eval
не соответствует этому шаблону, потому что функция safediv
, используемая для обработки результирующих значений, не является чистой функцией, но может сама по себе завершиться ошибкой.
Я не Haskell программист, но из типа eval (Div x y)
кажется, что это Maybe(Maybe Int)
- который может быть просто сжато , нет? (Что-то вроде flatten
в Scala или join
в Haskell). Что на самом деле является проблемой здесь?
Независимо от того, x,y
Just(s)/Nothing(s)
кажется, safediv
будет правильно оценивать - единственная проблема здесь - это тип возвращаемого значения, которое можно преобразовать соответственно. Как именно автор go из своего рассуждения к такому выводу это то, что мне трудно понять.
... ограничения аппликативного стиля ограничивают нас применением чистых функций к эффективным аргументам
Кроме того, почему параграф, отмеченный (X)
выше, делает это утверждение, когда проблема только Кажется, что это или возвращаемое смещение типа.
Я понимаю, что аппликативы могут быть использованы для более эффективной цепочки вычислений, когда результаты одного не влияют на другой - но в этом случае я довольно запутан относительно того, как / где произойдет сбой, и если простое исправление типа возврата решит проблему:
eval (Div x y) = join(pure safediv <*> eval x <*> eval y)
А safediv
должен быть чистым ? AFAIK это также может быть типа F[Maybe]
или F[Either]
, нет? Что я могу пропустить? Я вижу , куда он идет, но не уверен, что это правильный пример, чтобы попасть туда ИМХО.