Хранить результат применения функций в кортеже внутри DO-блока
Хотя я могу применить функцию два раза и связать результат в кортеж:
let foo :: Num a => a -> a
foo x = x + 1
let (x,y) = (foo 10, foo 20)
Это не может быть сделано (хотя бы я не знаю, как это сделать правильно) в течение do
блок:
let bar :: Num a => a -> IO a
bar x = do
let y = x + 1
return y
let test :: Num a => IO a
test = do
(x,y) <- (bar 10, bar 20)
return y
Я получил следующую ошибку при наборе GHCI REPL:
:29:15:
Couldn't match expected type ‘IO a1’ with actual type ‘(t0, a)’
Relevant bindings include
test :: IO a (bound at :28:5)
In the pattern: (x, y)
In a stmt of a 'do' block: (x, y) <- (bar 10, bar 20)
In the expression:
do { (x, y) <- (bar 10, bar 20);
return y }
:29:24:
Couldn't match type ‘(,) (IO a0)’ with ‘IO’
Expected type: IO (IO a1)
Actual type: (IO a0, IO a1)
In a stmt of a 'do' block: (x, y) <- (bar 10, bar 20)
In the expression:
do { (x, y) <- (bar 10, bar 20);
return y }
In an equation for ‘test’:
test
= do { (x, y) <- (bar 10, bar 20);
return y }
Очевидно, я могу решить это более многословно:
let test' :: Num a => IO a
test' = do
x <- bar 10
y <- bar 20
return y
Есть ли правильный способ выразить test
не делая это как test'
?
3 ответа
import Control.Applicative
test = do (x,y) <- (,) <$> bar 10 <*> bar 20
return y
Ака (x,y) <- liftA2(,) (bar 10) (bar 20)
,
Конечно, для этого конкретного примера (где x
просто выбрасывается) было бы эквивалентно и гораздо лучше просто написать
test = bar 20
Я позволю себе предложить несколько изменений в вашем коде. Вот моя версия:
import Control.Monad
-- no need for the do and let
bar :: Num a => a -> IO a
bar x = return $ x + 1 -- or: bar = return . (1+)
-- liftM2 to make (,) work on IO values
test :: Num a => IO a
test = do (x,y) <- liftM2 (,) (bar 10) (bar 20) -- or: (,) <$> bar 10 <*> bar 20
return y
-- show that this actually works
main :: IO ()
main = test >>= print
Ваши типы не совпадают: ваш (bar 10, bar 20)
оценивает, чтобы набрать Num a => (IO a, IO a)
но вы относитесь к этому как Num a => IO (a, a)
, Поднимая (,)
мы заставляем это работать на IO
значения и возвращая IO
значение.
Посмотрите на это (GHCi, import Control.Monad
получить liftM2
):
:t (,)
-- type is :: a -> b -> (a, b)
:t liftM2 (,)
-- type is :: Monad m => m a -> m b -> m (a, b)
в нашем случае Monad
это IO
монада. Таким образом, окончательный вывод liftM2 (,)
будет хорошо работать внутри IO
do-block, потому что он возвращает правильный IO
значение.
И, конечно, вы можете решить эту конкретную проблему с помощью менее многословного:
test'' = bar 20
PS: Пожалуйста, не возвращайте вещи в IO
Монада без какой-либо причины. Вы делаете совершенно чистые операции выглядящими не чистыми, и нет разумного пути назад.
Вам понадобится вспомогательная функция, чтобы поднять IO
Ради кортежа из кортежа я буду использовать Int
вместо Num a => a
(bar 1, bar 2) :: (IO Int, IO Int)
Таким образом, вам нужно что-то с подписью
liftTuple :: (IO x, IO y) -> IO (x, y)
liftTuple (mx, my) = ...
тогда вы можете сделать (x,y) <- liftTuple (bar 1, bar 2)