2013-03-10 3 views
8

Когда я, казалось, понял, для чего вернется возвращение в Haskell, я попытался сыграть с различными альтернативами, и кажется, что возвращение не только может быть использовано где угодно в цепочке монад, но также может быть полностью исключитьЧто такого особенного в ключе 'return'

*Main> Just 9 >>= \y -> (Just y) >>= \x -> return x 
Just 9 

*Main> Just 9 >>= \y -> (return y) >>= \x -> (Just y) 
Just 9 

*Main> Just 9 >>= \y -> (Just y) >>= \x -> (Just x) 
Just 9 

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

data MaybeG a = NothingG | JustG a deriving Show 
instance Monad MaybeG where 
    -- return x = JustG x 
     NothingG >>= f = NothingG 
     JustG x >>= f = f x 
     fail _ = NothingG 

Monad.hs:3:10: 
    Warning: No explicit method nor default method for `return' 
    In the instance declaration for `Monad MaybeG' 

и я все еще могу использовать монады

*Main> JustG 9 >>= \y -> (JustG 11) >>= \x -> (JustG y) 
JustG 9 

*Main> JustG 9 >>= \y -> (NothingG) >>= \x -> (JustG y) 
NothingG 

Так что же такого особенного в ключевом слове return? Это более сложные случаи, когда я не могу его опустить? Или потому, что это «правильный» способ делать вещи, даже если они могут быть сделаны по-другому?

UPDATE: .. или другая альтернатива, я мог бы определить мой собственного конструктор монадического значения

finallyMyLastStepG :: Int -> MaybeG Int 
finallyMyLastStepG a = JustG a 

и производит другой вариант тех же цепи (с тем же результатом)

*Main> JustG 9 >>= \y -> (JustG 11) >>= \x -> (finallyMyLastStepG y) 
JustG 9 
+6

'return' - это не ключевое слово. И да, он не выполняет поток управления, как ключевое слово с тем же именем, что и в большинстве императивных языков программирования, поэтому 'return ...' in 'do {foo; вернуть ...; quux} 'является избыточным. – delnan

+0

@ delnan, проблема в том, что когда я читаю все о монадах, «возвращение» на последней строке выглядит как требование. Но в моей третьей строке я заменил его прямым монадическим созданием ценности, и Haskell в порядке с этим. – Maksee

+3

@Maksee: Возвращение на последней строке не является обязательным требованием. Так получилось, что для монады Maybe возможно, что операция привязки содержит конструкцию новой простой монады. Обычно это не так; рассмотрим, скажем, тождественную монаду. Он не называет 'return' ничем в' bind'. –

ответ

10

Я подозреваю, что вы неправильно понимаете, что означает «возвращение» в контексте монады в Haskell. return is function, который принимает a и возвращает «завернутый a» - то есть самый простой экземпляр монады. На других языках его часто называют Unit. Это не «поток управления» return, который вы видите на языках C-типа.

Так что в вашем примере Maybe монады, мы возврат определяется как функция, которая принимает в a и возвращает Maybe a:

return :: a -> Maybe a 

А что она делает? если вы даете ему x, это дает Вам Just x:

И теперь вы можете использовать return как стенографии, когда вам нужна эта функция, а не выписывать:

\x -> Just x 

Это называется return, потому что когда вы пишете монады в нотации do, похоже, что вы сделали бы на C-подобном языке.

+0

Ok, \ x -> (Just x) в моей третьей строке делает то же самое, он возвращает монадическое значение – Maksee

+0

Это называется 'Unit'? AFAIK 'Unit' как альтернативное имя для пустого кортежа (или более абстрактно/вообще/правильно, тип с одним значением). – delnan

+0

@Maksee: Правильно. –

24

Так что же такого особенного в ключевом слове return?

Во-первых, return является не ключевое слово в Haskell. Это перегруженная функция.

Ее тип определяется по формуле:

class Monad m where 
    -- | Sequentially compose two actions, passing any value produced 
    -- by the first as an argument to the second. 
    (>>=)  :: m a -> (a -> m b) -> m b 

    -- | Inject a value into the monadic type. 
    return  :: a -> m a 

Итак, вы видите, что return является функцией, которая присваивается значение типа a, возвращает новое значение типа m a, где m некоторый тип, который является экземпляром от Monad. Такие типы включают в себя:

  • Monad []
  • Монадой I0
  • Монада Maybe
  • Монада STM
  • Монада ((->) r)
  • Монада (Either e)
  • Монада (ST s)

и многое другое. Экземпляры «Монада» должны удовлетворять следующим условиям:

> return a >>= k == k a 
> m >>= return == m 
> m >>= (\x -> k x >>= h) == (m >>= k) >>= h 

Реализация функции a -> m a довольно легко догадаться. Вот определение наиболее распространенных монад:

Списки:

return x = [x] 

Может

return x = Just x 

Итак, вы видите, что return перегруженная функция, "лифты" а значение в монадическую обертку. Таким образом, вы можете использовать его везде, где вы можете использовать его определение. Например.

Prelude> 1 : return 2 
[1,2] 

или в the do notion (полезно, когда chaining expressions).

> do v <- return 7 ; return v :: Maybe Int 
Just 7 

Реальная причина для использования монадического return когда сочинял несколько значений в некоторой монаде:

Prelude> do x <- return 1 ; y <- return 2 ; return (x + y) :: Maybe Int 
Just 3 
Prelude> do x <- Nothing ; y <- return 2 ; return y 
Nothing 

В последнем заявлении вы видите, как цепь короткого замыкания, когда он ударил нулевое значение данной монады. В этом случае Nothing.

Реферат: return - перегруженная функция, которая поднимает значение в монадическую оболочку. Вы используете его, когда вам нужно поднять значения. Это не ключевое слово control-flow, так как оно находится на императивных языках.

+0

Хорошо, я вижу, что применение «ключевого слова» к «возврату» неверно, но в основном мой вопрос касался того, должен ли я всегда использовать его, или я могу безопасно использовать другой способ сделать то же самое. Другими словами, видя, что один и тот же результат может быть возвращен несколькими другими способами, почему я должен следовать конвенции? – Maksee

+0

Итак, я могу заменить ваш последний пример на _do x <- (Just 1); y <- (Just 2); (Just (x + y)) _ и получить тот же результат без использования «return» – Maksee

+0

@Maksee: Да, он делает то же самое. – Vitus

1

Комментарий Майка Хартла привел меня в правильном направлении, хотя и не был таким формальным имхо, поэтому я просто излагаю свое окончательное понимание того, что так особенного в операторе «вернуться».

Любые типы операторов классов, которые он поддерживает, и есть функции, которые могут работать только в этом контексте класса (навязанные через класс constratint symbol =>).Так, например, подпись filterM

filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] 

показывает, что его можно использовать только в монадическом контексте. Магия заключается в том, что в теле эта функция может свободно использовать любой оператор, который имеет класс (>> = и return для Monad), и если экземпляр (например, мой MaybeG) не имеет метода (возврат в моем случае), то функция может потерпеть неудачу. Поэтому, когда возвращение есть

> filterM (\x -> JustG (x > 0)) [2, 1, 0, -1] 
JustG [2,1] 

и когда он комментировал (см моя реализация MaybeG в вопросе)

> filterM (\x -> JustG (x > 0)) [2, 1, 0, -1] 

*** Exception: Monad.hs:3:10-21: No instance nor default method for class operation GHC.Base.return 

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

Я думаю, что мое первоначальное недоразумение было связано с тем, что большинство учебников объясняют монадические цепочки без полиморфного (ad hoc) контекста. Этот контекст, на мой взгляд, делает монады более мощными и многоразовыми.

+2

Даже если вы знаете экземпляр «Monad», который используете, вы можете не знать, как он реализован, поэтому вы должны использовать 'return' для отмены значения. Например, как бы вы подняли значение в 'IO' без' return'? – pat

Смежные вопросы