Оценка Haskell и ленивых монад - PullRequest
       44

Оценка Haskell и ленивых монад

7 голосов
/ 17 октября 2011

Играя с монадами, я часто сталкиваюсь с проблемами оценки.Теперь я понимаю основные понятия ленивых вычислений, но не понимаю, как монады лениво оцениваются в Haskell.

Рассмотрим следующий код

module Main where
import Control.Monad
import Control.Applicative
import System

main = print <$> head <$> getArgs

На мой взгляд, это должно бытьФункция main должна печатать первый аргумент консоли, но это не так.

Я знаю, что

getArgs :: IO [String]
head <$> getArgs :: IO String
print <$> (head <$> getArgs) :: IO (IO ())
main :: IO (IO ())

, поэтому, по-видимому, первый аргумент не выводится на стандартный вывод, поскольку содержимоеПервая монада IO не оценивается.Так что, если я присоединяюсь к двум монадам, это работает.

main = join $ print <$> head <$> getArgs

Кто-нибудь, пожалуйста, объясните мне это?(или дай мне указатель)

Ответы [ 2 ]

11 голосов
/ 17 октября 2011

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 не является частью этого вычисления, система времени выполнения просто отбрасывает его. Хорошим введением в эти вопросы является вторая глава Саймона Пейтона Джонса "Борьба с неуклюжим отрядом" .

4 голосов
/ 17 октября 2011

У вас есть head <$> getArgs :: IO String и print :: Show a => a -> IO (), то есть значение в монаде и функция от простого значения до монады.Функция, используемая для составления таких вещей, - это оператор монадического связывания (>>=) :: Monad m => m a -> (a -> m b) -> m b.

. Итак, вы хотите, чтобы

main = head <$> getArgs >>= print

(<$>) или fmap, имели тип Functor f => (a -> b) -> f a -> f b,поэтому полезно, когда вы хотите применить функцию pure к некоторому значению в монаде, поэтому она работает с head, но не с print, так как print не является чистым.

...