Haskell 2010 Report (определение языка) говорит :
Значением программы является значение идентификатора main
в модуле.
Main
, который должен быть вычислением типа IO τ
для некоторого типа τ
.
Когда программа выполняется, вычисление main
выполняется, и его результат (типа τ
) отбрасывается.
Ваша функция main
имеет тип IO (IO ())
. Приведенная выше цитата означает, что оценивается только внешнее действие (IO (IO ())
), а его результат (IO ()
) отбрасывается. Как вы сюда попали? Давайте посмотрим на тип print <$>
:
> :t (print <$>)
(print <$>) :: (Show a, Functor f) => f a -> f (IO ())
Итак, проблема в том, что вы использовали fmap
в сочетании с print
. Глядя на определение Functor
экземпляр для IO
:
instance Functor IO where
fmap f x = x >>= (return . f)
вы можете видеть, что это сделало ваше выражение эквивалентным (head <$> getArgs >>= return . print)
. Чтобы сделать то, что вы изначально хотели, просто удалите ненужные return
:
head <$> getArgs >>= print
Или, что эквивалентно:
print =<< head <$> getArgs
Обратите внимание, что действия ввода-вывода в Haskell аналогичны другим значениям - их можно передавать и возвращать из функций, сохранять в списках и других структурах данных и т. Д. Действие ввода-вывода не оценивается, если оно не является частью основного вычисления. Чтобы «склеить» действия ввода-вывода вместе, используйте >>
и >>=
, а не fmap
(что обычно используется для отображения pure функций на значения в некотором «блоке» - в вашем случае, IO
).
Обратите внимание, что это связано не с ленивым вычислением, а с чистотой - семантически ваша программа представляет собой чистую функцию, которая возвращает значение типа IO a
, которое затем интерпретируется системой времени выполнения. Поскольку ваше внутреннее действие IO
не является частью этого вычисления, система времени выполнения просто отбрасывает его. Хорошим введением в эти вопросы является вторая глава Саймона Пейтона Джонса "Борьба с неуклюжим отрядом" .