Делать нотацию для монады в функции, возвращающей другой тип - PullRequest
0 голосов
/ 13 октября 2018

Есть ли способ написать do нотацию для монады в функции, тип возврата которой не относится к указанной монаде?

У меня есть основная функция, выполняющая большую часть логики кода, дополненная другой функцией, которая выполняет некоторые вычисления для нее в середине.Дополнительная функция может завершиться ошибкой, поэтому она возвращает значение Maybe.Я хочу использовать нотацию do для возвращаемых значений в основной функции.Приведу общий пример:

-- does some computation to two Ints which might fail
compute :: Int -> Int -> Maybe Int

-- actual logic 
main :: Int -> Int -> Int
main x y = do
  first <- compute x y
  second <- compute (x+2) (y+2)
  third <- compute (x+4) (y+4)
  -- does some Int calculation to first, second and third

Я намерен, чтобы first, second и third имели действительные значения Int, взятые из контекста Maybe, новыполнение вышеуказанного способа заставляет Haskell жаловаться на неспособность сопоставить типы Maybe Int с Int.

Есть ли способ сделать это?Или я иду в неправильном направлении?

Извините, если какая-то терминология используется неправильно, я новичок в Haskell и все еще пытаюсь обернуть все вокруг.

РЕДАКТИРОВАТЬ

main должен возвращать Int, без включения в Maybe, так как есть другая часть кода, использующая результат main в качестве Int.Результаты одного compute могут потерпеть неудачу, но они должны все вместе пройти (то есть, по крайней мере, один прошел бы) в main, и я ищу способ использовать запись do, чтобы вывести их изMaybe, выполните с ними несколько простых Int вычислений (например, возможно, обработав любое Nothing, возвращаемое как 0), и верните окончательное значение как Int.

Ответы [ 3 ]

0 голосов
/ 13 октября 2018

Ответ Виллема в основном идеален, но просто для того, чтобы по-настоящему разобраться, давайте подумаем о том, что произойдет, если вы сможете написать что-то, что позволит вам вернуть целое число.

Итак, у вас есть mainфункция с типом Int -> Int -> Int, давайте предположим реализацию вашей функции compute следующим образом:

compute :: Int -> Int -> Maybe Int
compute a 0 = Nothing
compute a b = Just (a `div` b)

Теперь это в основном безопасная версия функции целочисленного деления div :: Int -> Int -> Int, которая возвращает Nothingесли делитель 0.

Если бы вы могли написать основную функцию, которая вам нравится, которая возвращает Int, вы могли бы написать следующее:

unsafe :: Int
unsafe = main 10 (-2)

Это приведет к сбою second <- compute ... и возврату Nothing, но теперь вы должны интерпретировать свой Nothing как число, которое не годится.Это побеждает всю цель использования Maybe монады, которая безопасно фиксирует сбой.Конечно, вы можете присвоить значение по умолчанию Nothing, как описал Виллем, но это не всегда уместно.

В целом, когда вы находитесь внутри блока do, вы должны просто думать внутри "коробка "это монада и не пытайся сбежать.В некоторых случаях, например Maybe, вы можете выполнять unMaybe с функциями типа fromMaybe или maybe, но не в целом.

0 голосов
/ 13 октября 2018

У меня есть две интерпретации вашего вопроса, поэтому я отвечу на оба:

Суммируйте значения Maybe Int, которые равны Just n, чтобы получить Int

Для суммирования Maybe Int с, выбрасывая Nothing значений, вы можете использовать sum с Data.Maybe.catMaybes :: [Maybe a] -> [a], чтобы выбросить Nothing значений из списка:

sum . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]

Получить первоеMaybe Int значение, равное Just n как Int

Чтобы получить первое не-1023 * значение, вы можете использовать catMaybes в сочетании с listToMaybe :: [a] -> Maybe a для получения Just первое значение, если оно есть, или Nothing, если его нет, и fromMaybe :: a -> Maybe a -> a для преобразования Nothing в значение по умолчанию:

fromMaybe 0 . listToMaybe . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]

Если вы 'Для гарантированного , чтобы иметь хотя бы один успех, используйте head вместо:

head . catMaybes $ [compute x y, compute (x+2) (y+2), compute (x+4) (y+4)]
0 голосов
/ 13 октября 2018

Ну подпись по сути неверна.Результат должен быть Maybe Int:

main :: Int -> Int -> <b>Maybe Int</b>
main x y = do
  first <- compute x y
  second <- compute (x+2) (y+2)
  third <- compute (x+4) (y+4)
  <b>return</b> (first + second + third)

Например, здесь мы return (first + second + third), и return обернет их в Just конструктор данных.

Этопотому что ваш блок do неявно использует >>= из Monad Maybe, который определен как:

instance Monad Maybe where
    Nothing >>=_ = Nothing
    (Just x) >>= f = f x
    return = Just

Так что это означает, что он действительно "распакует" значения из Justконструктор данных, но в случае, если из него выйдет Nothing, это означает, что результатом всего блока do будет Nothing.

Это более или менее удобно для Monad Maybe предложения: вы можете выполнять вычисления в виде цепочки успешных действий, и в случае сбоя одного из них результат будет Nothing, в противном случае он будет Just result.

Таким образом, в конце концов вы не можете вернуть Int вместо Maybe Int, так как это вполне возможно - с точки зрения типов - что одно или несколько вычислений могут вернуть Nothing.

Однако вы можете "обработать" результат обработки блока do, если, например, добавитьзначение «по умолчанию», которое будет использоваться в случае, если одно из вычислений будет Nothing, например:

import Data.Maybe(fromMaybe)

main :: Int -> Int -> <b>Int</b>
main x y = <b>fromMaybe 0</b> $ do
  first <- compute x y
  second <- compute (x+2) (y+2)
  third <- compute (x+4) (y+4)
  return (first + second + third)

Здесь, в случае, если do -блок, таким образом, возвращает Nothing, мы его заменяемс 0 (вы, конечно, можете добавить другое значение в fromMaybe :: a -> Maybe a -> a в качестве значения в случае, если вычисление «провалилось»).

Если вы хотитечтобы вернуть первый элемент в списке Maybe s, то есть Just, тогда вы можете использовать asum :: (Foldable t, Alternative f) => t (f a) -> f a, поэтому вы можете написать свой main как:

-- first <i>non</i>-failing computation

import Data.Foldable(asum)
import Data.Maybe(fromMaybe)

main :: Int -> Int -> Int
main x y = fromMaybe 0 $ <b>asum</b> [
    compute x y
    compute (x+2) (y+2)
    compute (x+4) (y+4)
]

Обратите внимание, что asum может по-прежнему содержать только Nothing с, поэтому вам все равно нужно выполнить некоторую постобработку.

...