Каков тип переменной в do-notation здесь, в Haskell?
Коды ниже выглядят совершенно ясно:
do
x <- Just 3
y <- Just "!"
Just (show x ++ y)
Здесь тип x
равен Num
, а y
- String
. (<-
здесь используется для получения фактического значения из Monad)
Однако этот сниппет выглядит не так ясно для меня:
import Control.Monad.Instances
addStuff :: Int -> Int
addStuff = do
a <- (* 2)
b <- (+ 10)
return (a + b)
Каков тип a
и тип b
здесь? Кажется, они действуют как Num
, но a <- (* 2)
и b <- (+ 10)
выглядят загадочными здесь...
Есть ли у кого-нибудь идеи об этом?
Ответы
Ответ 1
Ну, ты наткнулся на какую-то странную монаду.
Предполагаемая монада - это Monad ((->) r)
. Теперь, что это значит? Ну, это монада функций вида r -> *
. I.e. функций, которые принимают один и тот же тип ввода.
Вы спросили, что такое тип a
и b
в этом случае. Ну, они оба Num a => a
, но это не очень многое объясняет.
Интуитивно мы можем понять монаду следующим образом: Монадическое значение - это функция, которая принимает значение типа r
в качестве входного. Всякий раз, когда мы связываем в монаде, мы берем это значение и передаем его связанной функции.
Т.е. в нашем примере addStuff
, если мы назовем addStuff 5
, то a
привязано к (*2) 5
(которое равно 10
), а b
привязано к (+10) 5
(что 15
).
Посмотрите более простой пример из этой монады, чтобы попытаться понять, как это работает:
mutate = do a <- (*2)
return (a + 5)
Если мы отбросим это на привязку, получим:
mutate = (*2) >>= (\a -> return (a + 5))
Теперь это не очень помогает, поэтому используйте определение bind для этой монады:
mutate = \ r -> (\a -> return (a + 5)) ((*2) r) r
Это сводится к
mutate = \ r -> return ((r*2) + 5) r
Мы используем определение return
const
, можем уменьшить до
mutate = \ r -> (r*2) + 5
Какая функция, которая умножает число на 2, а затем добавляет 5.
Эта странная монада.
Ответ 2
Учитывая addStuff
addStuff :: Int -> Int
addStuff = do
a<-(*2)
b<-(+10)
return (a+b)
определение desugars в
addStuff =
(* 2) >>= \a ->
(+ 10) >>= \b ->
return (a + b)
Наведите указатель мыши на >>=
в fpcomplete онлайн-редактор.
:: Monad m => forall a b.
(m a ) -> (a -> m b ) -> (m b )
:: (Int -> a ) -> (a -> Int -> b ) -> (Int -> b )
:: (Int -> Int) -> (Int -> Int -> Int) -> (Int -> Int)
Это заставляет нас думать, что мы используем экземпляр Monad для функций. Действительно, если мы посмотрим на исходный код, мы увидим
instance Monad ((->) r) where
return = const
f >>= k = \ r -> k (f r) r
Используя эту недавно полученную информацию, мы можем сами оценить функцию addStuff
.
Учитывая начальное выражение
(* 2) >>= ( \a -> (+10) >>= \b -> return (a + b) )
подставляем с помощью определения >>=
, давая нам (в следующих {}
, []
, ()
просто иллюстрируем различную глубину ()
)
\r1 -> {\a -> (+10) >>= \b -> return (a + b)} {(* 2) r1} r1
упростить второй-последний термин внутри самой внешней лямбда
\r1 -> {\a -> (+10) >>= \b -> return (a + b)} {r1 * 2} r1
применить {r1 * 2}
к {\a -> ...}
\r1 -> {(+10) >>= \b -> return ((r1 * 2) + b)} r1
замените оставшийся >>=
своим определением снова
\r1 -> {\r2 -> [\b -> return (r1 * 2 + b)] [(+10) r2] r2} r1
упростить второй-последний термин внутри внутренней лямбда
\r1 -> {\r2 -> [\b -> return (r1 * 2 + b)] [r2 + 10] r2} r1
применить [r2 + 10]
к {\b -> ...}
\r1 -> {\r2 -> [return (r1 * 2 + (r2 + 10))] r2} r1
примените r1
к {\r2 -> ...}
\r1 -> {return (r1 * 2 + r1 + 10) r1}
замените return
своим определением
\r1 -> {const (r1 * 2 + r1 + 10) r1}
оцените const x _ = x
\r1 -> {r1 * 2 + r1 + 10}
приукрасить
\x -> 3 * x + 10
наконец, получим
addStuff :: Int -> Int
addStuff = (+ 10) . (* 3)