Калі рабіць-пазначэнняў варта пазбягаць у Haskell?

Большасць падручнікаў Haskell навучыць выкарыстанне рабіць-абазначэння для ўводу-высновы.

Я таксама пачаў з рабіць-натацыі, але гэта робіць мой код выглядаць як імператыўнага мовы больш, чым мова FP.

This week I saw a tutorial use IO with <$>

stringAnalyzer <$> readFile "testfile.txt"

instead of using do

main = do
    strFile <- readFile "testfile.txt"
    let analysisResult = stringAnalyzer strFile
    return analysisResult

І інструмент аналізу лог скончаны без зрабіць .

Так што маё пытанне «<�моцны> мы павінны пазбягаць рабіць натацыю ў любым выпадку? ».

Я, можа быць, ведаю, зрабіць будзе зрабіць код лепш у некаторых выпадках.

Also, why do most tutorials teach IO with do?

In my opinion <$> and <*> makes the code more FP than IO.

35
Толькі выкарыстаць яго, калі ён робіць рэчы прасцей. Напрыклад, ніколі не выкарыстоўваць рабіць абазначэння для аднаго радка. (Як, ніколі не Асноўныя = зрабіць друк «Hello World» . Выкарыстоўвайце галоўная = друк «Hello World» замест гэтага.) Пераканайцеся, што вы разумееце манады ў тэрмінах >> = і вяртанне . Калі вы зразумееце гэта, вы можаце выкарыстоўваць зрабіць .
дададзена аўтар PyRulez, крыніца
Q2: Павінны падручнікі навучыць IO з зрабіць , таму што (а) аппликативно навей і менш добра вядома, (б) рабіць абазначэння бліжэй да імператыўнага коду, які многія студэнты вучацца першым, і (с) Ёсць некаторыя рэчы, якія вы можаце зрабіць з манадай, што вы не можаце зрабіць з аппликативномом, галоўным чынам змяніць тое, што код называецца на аснове значэнні раней выснова.
дададзена аўтар AndrewC, крыніца
Q1: Прыкладное выдатны, чысты і вельмі функцыянальны стыль. Майстар яго і выглядваць, як шмат магчымасцяў, як вы можаце ачысціць ваш код з трохі *> і г.д., але вам трэба будзе выкарыстоўваць ўсю моц манадай часам і калі вы робіце, зрабіць абазначэння, як правіла, самы яскравы.
дададзена аўтар AndrewC, крыніца

7 адказы

In my opinion <$> and <*> makes the code more FP than IO.

Haskell не з'яўляецца чыста функцыянальным мовай, таму што «выглядае лепш». Часам гэта адбываецца, часта гэта не так. Прычына знаходжання функцыяналу не з'яўляецца яго сінтаксіс, але яго Семантыка . Яна узбройвае нас спасылачныя празрыстасці, што робіць яго значна прасцей даказаць інварыянтаў, дазваляе вельмі высокага ўзроўню аптымізацый, дазваляе лёгка пісаць агульнага прызначэння код і г.д ..

None of this has much to do with syntax. Monadic computations are still purely functional – regardless of whether you write them with do notation or with <$>, <*> and >>=, so we get Haskell's benefits either way.

However, notwithstanding the aforementioned FP-benefits, it is often more intuitive to think about algorithms from an imperative-like point of view – even if you're accustomed to how this is implemented through monads. In these cases, do notation gives you this quick insight of "order of computation", "origin of data", "point of modification", yet it's trivial to manually desugar it in your head to the >>= version, to grasp what's going on functionally.

Прыкладное стыль, безумоўна, вялікае ў многіх адносінах, аднак яна па сваёй сутнасці пункту свабоднай. Гэта часта добрая рэч, але асабліва ў больш складаных праблем, гэта можа быць вельмі карысна даваць імёны на «часовыя» зменныя. Пры выкарыстанні толькі сінтаксіс «FP» Haskell, гэта патрабуе альбо лямбда ці відавочна названыя функцыі. Абодва маюць добрыя прэцэдэнты, але былы ўводзіць зусім трохі шуму прама ў сярэдзіне вашага кода, а другі, а разбурае «паток», так як ён патрабуе , дзе або хай змяшчаецца дзе-небудзь яшчэ, дзе вы яго выкарыстоўваць. <�Код> зрабіць , з другога боку, дазваляе ўвесці зменную з імем менавіта там, дзе вам гэта трэба, без увядзення якіх-небудзь перашкод на ўсіх.

37
дададзена
Я падтрымліваю гэты адказ. Каб пазбегнуць рабіць -notation толькі таму, што ён «выглядае імператыў» контрпрадуктыўна (гэта часта робіць код значна менш чытаным). Выкарыстоўвайце права інструмент </спраў> сінтаксіс для працы і г.д.
дададзена аўтар Daniel Fischer, крыніца

do notation in Haskell desugars in a pretty simple way.

do
  x <- foo
  e1 
  e2
  ...

ператвараецца ў

 foo >>= \x ->
 do
   e1
   e2

і

do
  x
  e1
  e2
  ...

у

x >>
do 
  e1
  e2
  ....

This means you can really write any monadic computation with >>= і return. The only reason why we don't is because it's just more painful syntax. Monads are useful for imitating imperative code, do notation makes it look like it.

The C-ish syntax makes it far easier for beginners to understі it. You're right it doesn't look as functional, but requiring someone to grok monads properly before they can use IO is a pretty big deterrent.

The reason why we'd use >>= і return on the other hі is because it's much more compact for 1 - 2 liners. However it does tend to get a bit more unreadable for anything too big. So to directly answer your question, No please don't avoid do notation when appropriate.

Lastly the two operators you saw, <$> і <*>, are actually fmap і applicative respectively, not monadic. They can't actually be used to represent a lot of what do notation does. They're more compact to be sure, but they don't let you easily name intermediate values. Personally, I use them about 80% of the time, mostly because I tend to write very small composable functions anyways which applicatives are great for.

37
дададзена
Гэта было карысна для мяне. Тым не менш, што базавы варыянт?
дададзена аўтар orm, крыніца
Desugaring вы апісваеце, сапраўды проста, але наіўна. Do-натацыя таксама ўключае апрацоўку памылак, звязаную з функцыяй манадай пацярпець няўдачу, але не кажучы ўжо пра гэта нічога. Тым не менш, гэты адказ быў вельмі карысным, і я спасылаецца гэта шмат разоў. +1 ад мяне.
дададзена аўтар Buttons840, крыніца

Я часта лаўлю сябе на першае напісанні монадического дзеянні ў зрабіць абазначэнне, затым рэфактарынг яго ўніз да выказвання простага монадический (або функториален). Гэта адбываецца ў асноўным, калі зрабіць блок аказваецца карацей, чым я чакаў. Часам я рэарганізаваць у процілеглым кірунку; гэта залежыць ад кода ў пытанні.

Маё агульнае правіла: калі зрабіць блок толькі пару радкоў гэта звычайна акуратней як кароткае выраз. Доўгі зрабіць -блок, верагодна, больш чытэльным, як гэта, калі вы не можаце знайсці спосаб, каб разбіць яго на больш дробныя, больш компонуемые функцыі.


У працоўным прыкладзе, вось як мы можам ператварыць ваш падрабязны фрагмент кода на просты.

main = do
    strFile <- readFile "testfile.txt"
    let analysisResult = stringAnalyzer strFile
    return analysisResult

Па-першае, звярніце ўвагу, што апошнія два радкі маюць выгляд хай х = у наўзамен х . Гэта, вядома, можа быць ператвораны ў проста вяртанне ў .

main = do
    strFile <- readFile "testfile.txt"
    return (stringAnalyzer strFile)

Гэта вельмі кароткі зрабіць блок: мы звязваем ReadFile «testfile.txt» на імя, а затым зрабіць што-то для гэтага імя ў наступным радку. Давайце паспрабуем «дэ-обсахаривания» гэта як будзе кампілятар:

main = readFile "testFile.txt" >>= \strFile -> return (stringAnalyser strFile)

Look at the lambda-form on the right hand side of >>=. It's begging to be rewritten in point-free style: \x -> f $ g x becomes \x -> (f . g) x which becomes f . g.

main = readFile "testFile.txt" >>= (return . stringAnalyser)

Гэта ўжо шмат акуратней, чым арыгінальны зрабіць блок, але мы можам пайсці далей.

Here's the only step that requires a little thought (though once you're familiar with monads and functors it should be obvious). The above function is suggestive of one of the monad laws: (m >>= return) == m. The only difference is that the function on the right hand side of >>= isn't just return - we do something to the object inside the monad before wrapping it back up in a return. But the pattern of 'doing something to a wrapped value without affecting its wrapper' is exactly what Functor is for. All monads are functors, so we can refactor this so that we don't even need the Monad instance:

main = fmap stringAnalyser (readFile "testFile.txt")

Finally, note that <$> is just another way of writing fmap.

main = stringAnalyser <$> readFile "testFile.txt"

Я думаю, што гэтая версія нашмат больш зразумела, чым зыходны код. Яна можа быць прачытана як прысуд: « Асноўны гэта stringAnalyser прымяняецца да выніку чытання » testfile.txt «». Арыгінальная версія балоты Вас у працэдурных дэталях яго працы.


Addendum: my comment that 'all monads are functors' can in fact be justified by the observation that m >>= (return . f) (aka the standard library's liftM) is the same as fmap f m. If you have an instance of Monad, you get an instance of Functor 'for free' - just define fmap = liftM! If someone's defined a Monad instance for their type but not instances for Functor and Applicative, I'd call that a bug. Clients expect to be able to use Functor methods on instances of Monad without too much hassle.

32
дададзена
На шчасце, база 4.8 робіць Аппликативные (і, такім чынам, Functor ) суперкласса манадай , вялікае змяненне разрыўной, што ніхто ніколі не скардзіцца. Так хутка, не будзе ніякай неабходнасці нават <�я> думаю </я> аб тым, ваш манадай гэта Functor !
дададзена аўтар dfeuer, крыніца
<�Р> Калі мы пазбягаем рабіць натацыю ў любым выпадку?

Я б сказаў, што <�моцны> вызначана няма . Для мяне самым важным крытэрыем у такіх выпадках <�моцны>, каб зрабіць код у максімальна чытэльным і зразумелым, наколькі гэта магчыма . <�Код> зрабіць -notation быў уведзены, каб зрабіць монадический код больш зразумелым, і гэта тое, што мае значэнне. Вядома, у многіх выпадках, выкарыстоўваючы Прыкладное кропка вольнага абазначэння вельмі добра, напрыклад, замест таго,

do
    f <- [(+1), (*7)]
    i <- [1..5]
    return $ f i

we'd write just [(+1), (*7)] <*> [1..5].

Але ёсць шмат прыкладаў, калі не выкарыстоўваючы у -notation будзе зрабіць код вельмі нечытэльным. Разгледзім гэты прыклад :

nameDo :: IO ()
nameDo = do putStr "What is your first name? "
            first <- getLine
            putStr "And your last name? "
            last <- getLine
            let full = first++" "++last
            putStrLn ("Pleased to meet you, "++full++"!")

тут зусім ясна, што адбываецца і як IO дзеянні парадкуюцца. А зрабіць -бесплатно абазначэнне выглядае

name :: IO ()
name = putStr "What is your first name? " >>
       getLine >>= f
       where
       f first = putStr "And your last name? " >>
                 getLine >>= g
                 where
                 g last = putStrLn ("Pleased to meet you, "++full++"!")
                          where
                          full = first++" "++last

ці як

nameLambda :: IO ()
nameLambda = putStr "What is your first name? " >>
             getLine >>=
             \first -> putStr "And your last name? " >>
             getLine >>=
             \last -> let full = first++" "++last
                          in  putStrLn ("Pleased to meet you, "++full++"!")

якія з'яўляюцца значна менш чытэльным. Вядома, тут зрабіць -notation значна пераважней тут.

If you want to avoid using do, try structuring your code into many small functions. This is a good habit anyway, and you can reduce your do block to contain only 2-3 lines, which can be then replaced nicely by >>=, <$>,<*>` etc. For example, the above could be rewritten as

name = getName >>= welcome
  where
    ask :: String -> IO String
    ask s = putStr s >> getLine

    join :: [String] -> String
    join  = concat . intersperse " "

    getName :: IO String
    getName  = join <$> traverse ask ["What is your first name? ",
                                      "And your last name? "]

    welcome :: String -> IO ()
    welcome full = putStrLn ("Pleased to meet you, "++full++"!")

Гэта крыху больш, і, магчыма, трохі менш зразумелым для Haskell пачаткоўцаў (з-за перасыпаць , CONCAT і траверс ), але ў многіх выпадках тыя, новыя, невялікія функцыі могуць быць паўторна выкарыстаны ў іншых месцах коды, што зробіць яго больш структураваным і компонуем.


I'd say the situation is very similar to whether to use the point-free notation or not. In many many cases (like in the top-most example [(+1), (*7)] <*> [1..5]) the point-free notation is great, but if you try to convert a complicated expression, you will get results like

f = ((ite . (<= 1)) `flip` 1) <*>
     (((+) . (f . (subtract 1))) <*> (f . (subtract 2)))
  where
    ite e x y = if e then x else y

Гэта было б узяць мяне даволі шмат часу, каб зразумець гэта, не працуе код. [Спойлер ніжэй:]

f x = if (x <= 1) then 1 else f (x-1) + f (x-2)


<�Р> Акрамя таго, чаму большасць падручнікаў навучыць IO з рабіць?

Паколькі IO дакладна прызначаны для імітацыі ўладных вылічэнняў з пабочнымі эфектамі, і таму секвенирования выкарыстоўваючы зрабіць вельмі натуральна.

9
дададзена
@DanielFischer Дзякуй за прапановы, я абнавіў прыклад. (Так, ён быў створаны з дапамогай інструмента каманднага радка для ліквідацыі абстракцыі.)
дададзена аўтар Petr Pudlák, крыніца
Ваша кропка свабоднай е усё роўна будзе цяжка пракрасціся, калі ён выкарыстоўваецца больш лёгкачытальным (<= 1) і адымаем 1 (соотв. 2 ) замест (<=) `flip` 1 і (-)` flip` 1 (шчыра кажучы, любы, хто выкарыстоўвае гэта павінен быць інструмент, спадзяюся каманднага радка інструмент). Добра аргументаваны адказ.
дададзена аўтар Daniel Fischer, крыніца

Прыкладное стыль варта заахвочваць, таму што ён складае (і гэта прыгажэе). Monadic стыль неабходны ў некаторых выпадках. См https://stackoverflow.com/a/7042674/1019205 для ў глыбіні тлумачэнні.

9
дададзена

do notation is just a syntactic sugar. It can be avoided in all cases. However, in some cases replacing do with >>= and return makes code less readable.

Так, пытанні:

<�Р> «Ці павінны мы пазбегнуць заявы рабіць у любым выпадку?».

Засяродзьцеся на тым, што ваш код ясным і больш чытэльным. Выкарыстоўвайце зрабіць , калі гэта дапамагае, пазбегнуць яго інакш.

<�Р> А ў мяне быў яшчэ адно пытанне, чаму большасць навучальных праграм навучыць IO з рабіць?

Паколькі зрабіць робіць IO код лепш чытаным ў многіх выпадках.

Акрамя таго, большасць людзей, якія пачынаюць навучання Haskell мае імператыўны вопыт праграмавання. Падручнікі для пачаткоўцаў. Яны павінны выкарыстоўваць стыль, які лёгка зразумець прышэльцамі.

6
дададзена

The do notation is expanded to an expression using the functions (>>=) and (>>), and the let expression. So it is not part of the core of the language.

(>>=) and (>>) are used to combine actions sequentially and they are essential when the result of an action changes the structure of the following actions.

У прыкладзе, прыведзеным у пытанні гэта не відавочна, бо ёсць толькі адзін IO дзеянне, неабходна, такім чынам, няма паслядоўнасці.

Разгледзім, напрыклад, выраз

do x <- getLine
   print (length x)
   y <- getLine
   return (x ++ y)

што ў перакладзе на

getLine >>= \x ->
print (length x) >>
getLine >>= \y ->
return (x ++ y)

In this example the do notation (or the (>>=) and (>>) functions) is needed for sequencing the IO actions.

Так рана ці позна праграміст будзе трэба.

1
дададзена
рабіць-натацыя <�я> не неабходна. Гэта проста sytactic цукар. Ваш прыклад з'яўляецца няправільным --- звязаць аператар IO манады клапоціцца аб sequencial выканання.
дададзена аўтар KAction, крыніца
Чытайце больш уважліва, што я напісаў: без зрабіць абазначэння АБО функцыі (>> =) і (>>) ён можа быць пашыраны каб ...
дададзена аўтар Romildo, крыніца
Відавочна, што зрабіць абазначэння АБО функцыі (>> =) і (>>) неабходныя для секвенирования IO дзеянні, і што зрабіць абазначэння пашыраецца (гэта значыць, гэта сінтаксічны цукар) для прымянення гэтых функцый.
дададзена аўтар Romildo, крыніца
@hammar ў маім прыкладзе действие1 , action2 і действий3 пазначае агульныя дзеянні, якія могуць спатрэбіцца ў выніку папярэдніх дзеянняў. Больш рэалістычны прыклад зрабіць {х <- GetLine; друк (даўжыня х); у <- GetLine; Вяртанне (х ++ у)} дзе действие1 , действие2 , действий3 і F стаяць GetLine , друк (даўжыня х) , GetLine і (++) , адпаведна.
дададзена аўтар Romildo, крыніца
Я адрэдагаваў мой адказ, каб лепш растлумачыць ідэю, як гэта было прапанавана @hammar.
дададзена аўтар Romildo, крыніца