Понимание $ в Haskell - PullRequest
       9

Понимание $ в Haskell

2 голосов
/ 27 марта 2012

Мне трудно понять следующий код (выполняется в монаде ErrorT, наложенной поверх IO):

closePort [Port port] = liftIO $ hClose port >> (return $ Bool True)

>> имеет более высокий приоритет, чем $. Таким образом, Bool True сначала переносится в IO, а затем поднимается с помощью liftIO или hClose поднимается первым? Другими словами, >> и return выполняются в монаде IO или в монаде ErrorT?

Ответы [ 5 ]

14 голосов
/ 27 марта 2012

Вам не нужно беспокоиться о приоритете в этом случае, потому что

liftIO (hClose port >> return (Bool True))

и

liftIO (hClose port) >> return (Bool True)

должно быть эквивалентно из-за законов монадных трансформаторов, которые говорят, что

  1. Подъем return ничего не делает.

    lift . return = return
    
  2. Поднятие последовательности двух действий аналогично поднятию их отдельно.

    lift (m >>= f) = lift m >>= (lift . f)
    

liftIO также должен следовать этим законам, чтобы мы могли видеть, что

liftIO (hClose port >> return (Bool True))
= -- definition of >>
liftIO (hClose port >>= \_ -> return (Bool True))
= -- second monad transformer law
liftIO (hClose port) >>= \_ -> liftIO (return (Bool True))
= -- first monad transformer law
liftIO (hClose port) >>= \_ -> return (Bool True)
= -- definition of >>
liftIO (hClose port) >> return (Bool True)
7 голосов
/ 27 марта 2012

Данный код эквивалентен

closePort [Port port] = liftIO ( hClose port >> (return ( Bool True) ) )

, поэтому весь (hClose port) >> (return (Bool True)) является аргументом для liftIO. Таким образом, (>>) и return являются единицами IO, а затем все вычисления IO снимаются с liftIO.

1 голос
/ 28 марта 2012

Операторы с более высоким приоритетом связываются более тесно, чем операторы с низким приоритетом.

Чтобы выяснить порядок, когда несколько выражений находятся в выражении, начните с оператора с наивысшим приоритетом и поместите круглые скобки вокруг выражений с обеих сторон, а затем продолжите его до приоритета оператора. Для операторов одного уровня вы определяете порядок на основе их определенной ассоциативности. Недопустимо смешивать операторы одного и того же уровня приоритета, но с разным ассоциативным поведением, так как группировка тогда неоднозначна Процедура, вероятно, знакома по работе с числовыми операторами:

2 + 3 * 5 - 1 + 2
-- * is infixl 7
2 + (3 * 5) - 1 + 2
-- + and - are infixl 6, so apply parens starting at the left
(2 + (3 * 5)  - 1) + 2

((2 + (3 * 5)) - 1) + 2

Поскольку >> имеет более высокий приоритет, чем $, то же самое отношение применяется к

liftIO $ hClose port >> (return $ Bool True)

дает вам

liftIO $ (hClose port >> (return $ Bool True))

Итак, сначала hClose и return $ Bool True объединяются в выражение с типом IO (Bool'), которое затем поднимается с помощью liftIO. (Где Bool' - любой тип Bool True).

В отчете на Haskell подробно рассматривается синтаксис, в частности главы 2, 3 и 9.

1 голос
/ 27 марта 2012

(я предполагаю, Bool это конструктор данных определенного вами типа.)

liftIO $ hClose port >> (return $ Bool True)

такой же, как

liftIO (hClose port >> (return (Bool True)))

Так что return и >> являются версиями IO, а результат >> поднимается во внешнюю монаду.

0 голосов
/ 27 марта 2012

Возможно, вы захотите взглянуть на реализацию $:

($) :: (a -> b) -> a -> b
f $ x = f x

Это означает, что x будет оцениваться, если требуется f.До этого у нас есть это

f $ expression for x = f (expression for x)

В вашем случае, у нас есть это

x = hClose port >> (return ( Bool True) )
f = liftIO

, что означало бы, что

f $ expression for x = f (expression for x)
                     = liftIO (expression for x)
                     = liftIO (hClose port >> (return ( Bool True) ))

Я надеюсь, что это прояснит.

...