Хранить результат применения функций в кортеже внутри 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)

Другие вопросы по тегам