Всегда ли Haskell знает, какой «возврат» вызывать?

#haskell #monads

#хаскелл #монады

Вопрос:

Я определяю экземпляр монады следующим образом:

 data Something = Something a

instance Monad Something where
    return a = Something a        --Wraps a in 'Something', correct?
    m >>= f = do
        var <- m
        return $ f var            --I want this to pass var to f, then wrap the result
                                  --back up in the 'Something' monad with the return I
                                  --Just defined
  

Вопросы ->

1: Есть ли какие-либо явные ошибки / заблуждения в том, что я делаю?

2: Будет ли Хаскелл знать, чтобы вызвать возврат, который я определил выше, из m >>= f

3: Если я по какой-то причине определяю другую функцию

 f :: Something a -> Something b
f x = do
    var <- x
    return $ doMagicTo x
  

return Вызовет возврат, который я определил в экземпляре монады, и обернет x Something ?

Ответ №1:

Здесь есть несколько больших проблем.

Во-первых, Monad экземпляр должен иметь вид * -> * . Это означает, что им нужна хотя бы одна переменная типа, где у Something вас ее нет. Для сравнения:

 -- kind * -> *
Maybe
IO
Either String

-- kind *
Maybe Int
IO ()
Either String Double
  

Посмотрите, как работает каждый из Maybe , IO , и Either String вам нужен параметр типа, прежде чем вы сможете их использовать? При Something этом параметру типа нет места для заполнения. Итак, вам нужно изменить свое определение на:

 data Something a = Something a
  

Вторая большая проблема заключается в том, что >>= в вашем экземпляре Monad неверно. Обычно вы не можете использовать do-нотацию, потому что это просто вызывает Monad функции return и >>= . Поэтому вам нужно записать это без каких-либо функций монады, будь то do-нотация или вызов >>= или return .

 instance Monad Something where
    return a = Something a        --Wraps a in 'Something'
    (Something m) >>= f = f m     --unwraps m and applies it to f
  

Определение >>= проще, чем вы ожидали. Развернуть m легко, потому что вам просто нужно сопоставить шаблон с Something конструктором. Кроме f :: a -> m b того, поэтому вам не нужно беспокоиться о том, чтобы снова обернуть его, потому f что это для вас.

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

Имейте в виду, что нет ничего синтаксически неправильного в использовании do-нотации или >>= в объявлении экземпляра monad . Проблема в том, что >>= это определяется рекурсивно, поэтому программа переходит в бесконечный цикл, когда вы пытаетесь ее использовать.

(Примечание.Б Something . как определено здесь, это идентификационная монада)

Что касается вашего третьего вопроса, да return , функция, определенная в экземпляре Monad, является той, которая будет вызвана. Классы типов отправляются по типу, и, как вы указали, тип должен быть Something b тем, для которого компилятор автоматически будет использовать экземпляр Monad Something . (Я думаю, вы имели в виду, что последняя строка должна быть doMagicTo var ).

Комментарии:

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

Ответ №2:

Основная проблема заключается в том, что ваше определение >>= является циклическим.

Синтаксис do Хаскелла — это синтаксический сахар для цепочек >>= и >> , поэтому ваше определение

 m >>= f = do
    var <- m
    return $ f var         
  

Десугарирует в

 m >>= f = 
    m >>= var -> 
    return $ f var
  

Итак, вы определяете m >>= f как m >>= ... , который является круговым.

Что вам нужно сделать >>= , так это определить, как извлечь значение из m для передачи f . Кроме того, вы f должны возвращать монадическое значение, поэтому использование return здесь неверно (это ближе к тому, как вы бы определили fmap ).

Определение >>= for Something может быть:

 (Something a) >>= f = f a
  

Это монада идентичности — об этом много написано — это хорошая отправная точка для понимания того, как работают монады.

Ответ №3:

  1. Закрыть. Здесь return это избыточно, и вам нужно добавить параметр типа в ваш Something конструктор типов. Редактировать: этот код по-прежнему неверен. Определение >>= является циклическим. Смотрите Другие ответы для получения дополнительной информации.

     data Something a = Something a
    
    instance Monad Something where
        return a = Something a
        m >>= f = do
            var <- m
            f var
      
  2. Поскольку ваше определение для >>= находится под instance Monad Something where типом, >>= ‘s является Something a -> (a -> Something b) -> Something b . Таким образом, он может сказать, что f var это должно быть типа Something b . Термин для Google здесь — «вывод типа»

  3. ДА. Опять же, это вывод типа. Если компилятор не может определить нужный вам тип, он сообщит вам об этом. Но обычно это возможно.

Комментарии:

1. Смотрите Решение Джона Л.: вы не можете использовать do-блок для такого определения >>= , потому что, как вы думаете, как do-блок очищается от сахара? (Также указано в решении Рэмпиона.)

2. О, хороший звонок. Теперь я, должно быть, думаю больше как программист на Haskell, поскольку я просто взял код OP, модифицировал его, пока он не проверил тип и не скомпилировал, и сказал: «Да, это должно быть правильно». : P Тем не менее, я думаю, что вопрос OP был более или менее «Делает ли Haskell вывод типа?» хотя они не спрашивали об этом таким образом. И моя главная мысль была в том, что да, это так.