Аппликативный и монадический стиль для простого примера ввода-вывода

Вот две очень простые функции f и g.

{-# LANGUAGE ScopedTypeVariables #-}

module Test where

import Control.Applicative

f :: IO ()
f = do
    y <- (<*>) (pure (show . (*10))) (read <$> readFile "data")
    writeFile "out" y

g :: IO ()
g = do
    y <- (readFile "data" >>= return . show . (*10) . read)
    writeFile "out" y

Файл читается и *10 в f пишется в аппликативном стиле с pure и (<*>). Файл читается и *10 в g записывается в монадическом стиле, с >>=. (Я намеренно избегал использования liftM в g, чтобы подчеркнуть вопрос ниже).

В чем семантическая разница между f и g? Или в данном случае это просто стилистический выбор?


person Rob Stewart    schedule 30.11.2013    source источник
comment
По крайней мере, монадический стиль выглядит намного более читабельным для меня :)   -  person Jani Hartikainen    schedule 30.11.2013
comment
Вы можете написать (<*>) (pure (show . (*10))) (read <$> readFile "data") как show . (*10) . read <$> readFile "data"   -  person bennofs    schedule 30.11.2013
comment
<- и return в g в основном компенсируют друг друга, do { y <- readFile "data"; writeFile "out" (show . (*10) . read $ y) } будет короче (и, возможно, менее надуманным).   -  person Frerich Raabe    schedule 30.11.2013


Ответы (2)


show . (*10) . read — это «немонадическая» функция, под которой я подразумеваю, что она ничего не делает в монаде IO, как видно из ее типа.

>>= return . можно сократить до

`liftM`

Но

`liftM`

всегда должен быть эквивалентен

`fmap`

fmap не нуждается ни в монадном классе типов, ни в аппликативном классе типов, ему просто нужен класс типов функторов.

Теперь обращаем наше внимание на аппликативную версию, это:

(<*>) (pure ...

эквивалентно <$>, что равнозначно fmap.

Таким образом, в обоих случаях мы «на самом деле» просто работаем с операцией функтора между чтением и записью, и хотя вы комбинируете функции немного по-разному (нам нужно применить один или несколько «законов», чтобы перевести два версии друг в друга), семантика идентична или должна быть идентична. Во всяком случае, они определенно связаны с монадой IO.

person Robin Green    schedule 30.11.2013

Да, выбор чисто стилистический, но оба можно привести в порядок, чтобы они были более идиоматическими:

Аппликативные функции лучше всего записывать в виде операторов и без точек:

f :: IO ()
f = show . (*10) . read <$> readFile "data" >>= writeFile "out"

В монадическом стиле это выглядит более аккуратно, если вы идете более искренне, не точечно, избегая операторов:

g :: IO ()
g = do 
    y <- readFile "data" 
    let x = show . (*10) . read $ y
    writeFile "out" x
person not my job    schedule 01.12.2013