#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:
-
Закрыть. Здесь
return
это избыточно, и вам нужно добавить параметр типа в вашSomething
конструктор типов. Редактировать: этот код по-прежнему неверен. Определение>>=
является циклическим. Смотрите Другие ответы для получения дополнительной информации.data Something a = Something a instance Monad Something where return a = Something a m >>= f = do var <- m f var
-
Поскольку ваше определение для
>>=
находится подinstance Monad Something where
типом,>>=
‘s являетсяSomething a -> (a -> Something b) -> Something b
. Таким образом, он может сказать, чтоf var
это должно быть типаSomething b
. Термин для Google здесь — «вывод типа» -
ДА. Опять же, это вывод типа. Если компилятор не может определить нужный вам тип, он сообщит вам об этом. Но обычно это возможно.
Комментарии:
1. Смотрите Решение Джона Л.: вы не можете использовать do-блок для такого определения
>>=
, потому что, как вы думаете, как do-блок очищается от сахара? (Также указано в решении Рэмпиона.)2. О, хороший звонок. Теперь я, должно быть, думаю больше как программист на Haskell, поскольку я просто взял код OP, модифицировал его, пока он не проверил тип и не скомпилировал, и сказал: «Да, это должно быть правильно». : P Тем не менее, я думаю, что вопрос OP был более или менее «Делает ли Haskell вывод типа?» хотя они не спрашивали об этом таким образом. И моя главная мысль была в том, что да, это так.