fmap в блок do завершается с ошибкой печати

Я пытаюсь понять, почему функция, которую я написал с блоком выполнения, не может быть переписана для fmap аналогичного лямбда-выражения по списку.

У меня есть следующее:

-- This works
test1 x = do 
        let m = T.pack $ show x
        T.putStrLn m

test1 1

Производит

1

Но

-- This fails
fmap (\x -> do 
              let m = T.pack $ show x
              T.putStrLn m
              ) [1..10]

-- And this also fails
fmap (\x -> do 
             T.putStrLn $ T.pack $ show x
                ) [1..10]

С ошибкой:

<interactive>:1:1: error:
    • No instance for (Show (IO ())) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it

Мой putStrLn согласуется между рабочим и нерабочим. Импорт такой же. Мой танец show-pack-putstrln, требуемый для печати, также согласуется между рабочим и нерабочим.

Что происходит, что использование печати меняется между рабочим и нерабочим?

Обновление 1

-- I was also surprised that this fails
fmap (T.putStrLn $ T.pack $ show) [1..10]
-- it seemed as similar as possible to the test1 function but mapped.

<interactive>:1:7: error:
    • Couldn't match expected type ‘Integer -> b’ with actual type ‘IO ()’
    • In the first argument of ‘fmap’, namely ‘(T.putStrLn $ pack $ show)’
      In the expression: fmap (T.putStrLn $ pack $ show) [1 .. 10]
      In an equation for ‘it’: it = fmap (T.putStrLn $ pack $ show) [1 .. 10]
    • Relevant bindings include it :: [b] (bound at <interactive>:1:1)
<interactive>:1:29: error:
    • Couldn't match type ‘() -> String’ with ‘String’
      Expected type: String
        Actual type: () -> String
    • Probable cause: ‘show’ is applied to too few arguments
      In the second argument of ‘($)’, namely ‘show’
      In the second argument of ‘($)’, namely ‘pack $ show’
      In the first argument of ‘fmap’, namely ‘(T.putStrLn $ pack $ show)’

Обновление 2

-- This lambda returns x of the same type as \x
-- even while incidentally printing along the way
fmap (\x -> do 
              let m = T.pack $ show x
              T.putStrLn $ m
              return x
              ) [1..10]

Но также терпит неудачу с:

<interactive>:1:1: error:
    • No instance for (Show (IO Integer)) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it

person Mittenchops    schedule 25.04.2019    source источник


Ответы (2)


Тип fmap f [1..10] — это [T], где T — тип возвращаемого значения f.

В вашем случае T = IO (), поэтому тип полного выражения [IO ()].

Действия ввода-вывода не могут быть распечатаны, поэтому GHCi жалуется, когда вы пытаетесь распечатать этот список. Возможно, вы захотите запустить эти действия вместо их печати, используя что-то вроде sequence_ (fmap f [1..10]).

В качестве альтернативы рассмотрите возможность отказа от fmap и вместо этого используйте что-то вроде

import Data.Foldable (for_)

main = do
   putStrLn "hello"
   for_ [1..10] $ \i -> do
      putStrLn "in the loop"
      print (i*2)
   putStrLn "out of the loop"
person chi    schedule 25.04.2019
comment
Спасибо, и это полезно, но когда я изменяю возвращаемый тип лямбды, чтобы он был таким же, как x, который входит как \x, как я делаю в обновлении 2, и просто выполняю печать как случайную, а не возвращаемую, Я все еще получаю ту же ошибку. Я понимаю, что ваш цикл for_ правильный, но почему мой пересмотренный fmap не работает так, как функция, которую я заменяю сверху? - person Mittenchops; 25.04.2019
comment
@Mittenchops fmap в списке создает список. В исходном коде не было списков. Попробуйте спросить GHCi о типе ваших выражений, вы увидите, что они имеют тип [IO ()] вместо IO something, поэтому вам нужен sequence_, если вы действительно хотите запустить список действий ввода-вывода. - person chi; 25.04.2019

Вы написали:

но когда я изменяю тип возвращаемого значения лямбды, чтобы он был таким же, как x, который входит как \x, как я делаю в обновлении 2...

Нет нет. Вы не знаете. Лямбда-функция возвращает значение своего последнего выражения. В вашей лямбда-функции есть только одно выражение — весь do { ... } блок определяет значение, которое является возвращаемым значением этой лямбда-функции. Не x. return принадлежит do, а не лямбда-выражению. Легче увидеть, если мы напишем это с явными разделителями, как

fmap (\x -> do {
              let m = T.pack $ show x ;
              T.putStrLn $ m ;
              return x
              } ) [1..10]

Блок do в целом имеет тот же монадический тип, что и каждый из его строковых операторов.

Одним из них является putStrLn ..., тип которого IO (). Итак, ваша лямбда-функция возвращает IO t для некоторого t.

И из-за return x t является типом x. У нас есть return :: Monad m => t -> m t, поэтому с m ~ IO получается return :: t -> IO t.

x исходит из списка аргументов Num t => [t], поэтому в целом у вас есть

Num t => fmap (fx :: t -> IO t) (xs :: [t]) :: [IO t]

or

           xs :: [t]
        fx    ::  t  ->  IO t
   ----------------------------
   fmap fx xs ::        [IO t]
person Will Ness    schedule 29.04.2019
comment
Это очень полезное объяснение. Благодарю вас! - person Mittenchops; 29.04.2019