Што такое монада?

Сцісла зірнуўшы на Хаскелла ў апошні час, што будзе кароткім, кароткім, практычным тлумачэннем таго, што такое монада?

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

1282
05 сент. зададзены ljs 05 сент. 2008-09-05 02:26 '08 у 02:26 2008-09-05 02:26
@ 47 адказаў
  • 1
  • 2

Па-першае: тэрмін монада трохі сэнсу, калі вы не матэматык. Альтэрнатыўны тэрмін - построитель вылічэнняў, які трохі больш апісвае тое, для чаго яны сапраўды карысныя.

Вы просіце практычных прыкладаў:

Прыклад 1: Разуменне спісу:

 [x*2 | x<-[1..10], odd x] 

Гэты выраз вяртае падвойныя ліку ўсіх няцотных лікаў у дыяпазоне ад 1 да 10. Вельмі карысна!

Аказваецца, гэта сапраўды проста сінтаксічны цукар для некаторых аперацый у манадай List. Такое ж разуменне спісу можна запісаць так:

 do x <- [1..10] guard (odd x) return (x * 2) 

Ці нават:

 [1..10] >>= (\x -> guard (odd x) >> return (x*2)) 

Прыклад 2: увод / выснова:

 do putStrLn "What is your name?" name <- getLine putStrLn ("Welcome, " ++ name ++ "!") 

Абодва прыкладу выкарыстоўваюць манады, построители вылічэнняў AKA. Агульнай тэмай з'яўляецца тое, што монада ланцужкі аперацый нейкім канкрэтным, карысным спосабам. У разуменні спісу аперацыі аб'яднаны ў ланцужок так, што калі аперацыя вяртае спіс, то наступныя аперацыі выконваюцца для кожнага элемента ў спісе. З іншага боку, монада ўводу-вываду выконвае аперацыі паслядоўна, але перадае "ўтоеную зменную", якая прадстаўляе "стан свету", што дазваляе нам пісаць код ўводу-вываду чыста функцыянальным чынам.

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

Іншым прыкладам з'яўляюцца выключэнні: пры выкарыстанні манады Error аперацыі аб'ядноўваюцца ў ланцужок так, што яны выконваюцца паслядоўна, за выключэннем выпадкаў, калі выдаецца памылка, і ў гэтым выпадку астатняя частка ланцужкі адмяняецца.

Сінтаксіс асэнсавання спісу і натацыя do з'яўляюцца сінтаксічных цукрам для аперацый ланцужкі з выкарыстаннем аператара >>= . Монада - гэта проста тып, які падтрымлівае аператар >>= .

Прыклад 3: парсер

Гэта вельмі просты парсер, які аналізуе альбо радок у двукоссі, альбо лік:

 parseExpr = parseString <|> parseNumber parseString = do char '"' x <- many (noneOf "\"") char '"' return (StringValue x) parseNumber = do num <- many1 digit return (NumberValue (read num)) 

Аперацыі char , digit і г.д. Даволі простыя. Яны альбо супадаюць, альбо не супадаюць. Цуд - гэта монада, якая кіруе патокам кіравання: аперацыі выконваюцца паслядоўна да таго часу, пакуль супадзенне не завершыцца, і ў гэтым выпадку монада вяртаецца да апошняга значэнні <|> і спрабуе наступную опцыю. Зноў жа, спосаб аб'яднання аперацый з некаторай дадатковай карыснай семантыкай.

Прыклад 4: асінхронныя праграмаванне

Прыведзеныя вышэй прыклады ёсць у Haskell, але, аказваецца, F # таксама падтрымлівае манады. Гэты прыклад скрадзены ў Дона Сайма :

 let AsyncHttp(url:string) = async { let req = WebRequest.Create(url) let! rsp = req.GetResponseAsync() use stream = rsp.GetResponseStream() use reader = new System.IO.StreamReader(stream) return reader.ReadToEnd() } 

Гэты метад выбірае вэб-старонку. GetResponseAsync з'яўляецца выкарыстанне GetResponseAsync - ён фактычна чакае адказу ў асобным патоку, у той час як асноўны паток вяртаецца з функцыі. Апошнія тры радкі выконваюцца ў спароджаных струмені, калі адказ атрыманы.

У большасці іншых моў вам прыйдзецца відавочна стварыць асобную функцыю для радкоў, якія апрацоўваюць адказ. async монада здольная "падзяліць" блок самастойна і адкласці выкананне другой паловы. (Сінтаксіс async {} паказвае, што паток кіравання ў блоку вызначаецца async манадай.)

Як яны працуюць

Так як жа монада можа рабіць усе гэтыя мудрагелістыя рэчы кіравання патокам? Што сапраўды адбываецца ў do-блоку (або вылічальным выразе, як яны называюцца ў F #), так гэта тое, што кожная аперацыя (у асноўным кожны радок) складзена ў асобную ананімную функцыю. Гэтыя функцыі затым аб'ядноўваюцца з дапамогай аператара bind (пішацца >>= у Haskell). Паколькі аперацыя bind аб'ядноўвае функцыі, яна можа выконваць іх так, як лічыць патрэбным: паслядоўна, шматкроць, у адваротным парадку, адкідаць некаторыя, выконваць некаторыя ў асобным струмені, калі адчувае сябе так, і гэтак далей.

У якасці прыкладу, гэта пашыраная версія IO-кода з прыкладу 2:

 putStrLn "What is your name?" >>= (\_ -> getLine) >>= (\name -> putStrLn ("Welcome, " ++ name ++ "!")) 

Гэта больш гідкі, але таксама больш відавочна, што на самой справе адбываецца. Аператар >>= з'яўляецца магічным кампанентам: ён прымае значэнне (злева) і аб'ядноўвае яго з функцыяй (справа), каб атрымаць новае значэнне. Гэта новае значэнне затым бярэцца аператарам next >>= і зноў аб'ядноўваецца з функцыяй для атрымання новага значэння. >>= можна разглядаць як міні-ацэншчык.

Звярніце ўвагу, што >>= перагружана для розных тыпаў, таму кожная монада мае ўласную рэалізацыю >>= . (Усе аперацыі ў ланцужку павінны быць аднаго тыпу, хоць у адваротным выпадку аператар >>= не будзе працаваць.)

Найпростая магчымая рэалізацыя >>= проста бярэ значэнне злева і прымяняе яго да функцыі справа і вяртае вынік, але, як ужо было сказана, карыснасць ўсяго шаблону заключаецца ў тым, што ў монадная рэалізацыя >>= .

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

падводзячы вынікі

У тэрмінах Haskell монада - гэта параметризованный тып, які з'яўляецца асобнікам класа тыпаў Monad, які вызначае >>= разам з некалькімі іншымі аператарамі. З пункту гледжання непрафесіяналы, монада - гэта проста тып, для якога вызначана аперацыя >>= .

Самі па сабе >>= - гэта проста грувасткі спосаб аб'яднання функцый, але пры наяўнасці натацыі, якая хавае "слясарнай справа", монадические аперацыі аказваюцца вельмі добрай і карыснай абстракцыяй, карыснай ў многіх месцах у мове. і карысна для стварэння ўласных міні-моў на мове.

Чаму манады цяжкія?

Для многіх тых, хто вывучае Хаскель манады з'яўляюцца перашкодай, якое яны наносяць, як цагляная сцяна. Справа не ў тым, што самі манады з'яўляюцца складанымі, а ў тым, што рэалізацыя абапіраецца на многія іншыя пашыраныя функцыі Haskell, такія як параметризованные тыпы, класы тыпаў і гэтак далей. Праблема ў тым, што ўвод-выснова Haskell заснаваны на монадах, і ўвод-выснова, верагодна, з'яўляецца адной з першых рэчаў, якую вы хочаце зразумець пры вывучэнні новай мовы - у рэшце рэшт, ствараць праграмы, якія не вырабляюць ніякіх праграм, не вельмі цікава. выхад. У мяне няма неадкладнага вырашэння гэтай праблемы "курыца-яйка", за выключэннем апрацоўкі ўводу-вываду як "чараўніцтва адбываецца тут", пакуль у вас не будзе дастатковага досведу працы з іншымі часткамі мовы. Шкадую.

Выдатны блог аб монадах: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

994
11 окт. адказ дадзены JacquesB 11 каст. 2008-10-11 18:31 '08 у 18:31 2008-10-11 18:31

Тлумачэнне "што такое монада" - гэта трохі падобна на выказванне "што такое лік?". Мы пастаянна выкарыстоўваем нумары. Але ўявіце, што вы сустрэлі чалавека, які нічога не ведаў пра лічбы. Як вы маглі б растлумачыць, якія лічбы? І як бы вы нават пачалі апісваць, чаму гэта можа быць карысна?

Што такое монада? Кароткі адказ: Гэта адмысловы спосаб аб'яднання аперацый.

Па сутнасці, вы пішаце крокі выканання і звязваеце іх разам са "функцыяй звязвання". (У Haskell гэта называецца >>= .) Вы можаце самі напісаць выклікі аператару звязвання, або вы можаце выкарыстоўваць сінтаксічны цукар, які прымушае кампілятар ўстаўляць гэтыя выклікі функцый для вас. Але ў любым выпадку кожны крок падзяляецца выклікам гэтай функцыі звязвання.

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

Гэта гучыць не занадта складана, ці не так? Але існуе больш за адзін від манады. Навошта? Як?

border=0

Ну, функцыя bind можа проста ўзяць вынік з аднаго кроку і перадаць яго на наступны крок. Але калі гэта "ўсё" монада робіць ... гэта на самай справе не вельмі карысна. І гэта важна разумець: кожная карысная монада робіць нешта яшчэ ў дадатак да таго, каб проста быць манадай. Кожная карысная монада мае "асаблівую сілу", якая робіць яе ўнікальнай.

(Манадай, якая нічога не робіць нічога асаблівага, называецца "роўнай манадай". Хутчэй, як функцыя ідэнтычнасці, гэта гучыць як цалкам бессэнсоўная рэч, але не атрымліваецца ... Але гэтая іншая гісторыя і гандаль;)

У прынцыпе, кожная монада мае сваю ўласную рэалізацыю функцыі bind. І вы можаце напісаць функцыю звязвання, каб яна рабіла памылкі паміж крокамі выканання. напрыклад:

  • Калі кожны крок вяртае індыкатар поспеху / збою, вы можаце звязаць выкананне наступнага кроку толькі ў тым выпадку, калі папярэдні атрымаў поспех. Такім чынам, няўдалы крок перапыняе ўсю паслядоўнасць "аўтаматычна", без якіх-небудзь умоўных праверак ад вас. (Failure Monad.)

  • Пашыраючы гэтую ідэю, вы можаце рэалізаваць "выключэння". (Памылка Monad або Выключэнне Monad.) Паколькі вы вызначаеце іх самастойна, а не з'яўляецеся моўнай функцыяй, вы можаце вызначыць, як яны працуюць. (Напрыклад, магчыма, вы хочаце ігнараваць першыя два выключэння і толькі перапыніць, калі абрана трэцяе выключэнне.)

  • Вы можаце прымусіць кожны крок вяртаць некалькі вынікаў і мець над імі функцыю прывязкі, падаючы кожны на наступны крок для вас. Такім чынам, вам не трэба працягваць пісаць завесы паўсюль, маючы справу з некалькімі вынікамі. Функцыя прывязкі "аўтаматычна" робіць усё гэта для вас. (Манадай спісу.)

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

  • Вы можаце зрабіць так, каб "дадатковыя дадзеныя" можна было замяніць. Гэта дазваляе імітаваць дэструктыўныя абнаўлення, ня вырабляючы пры гэтым дэструктыўных абнаўленняў. (State Monad і яго стрыечны брат Writer Monad.)

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

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

  • Вы можаце рэалізаваць "працягу" ў якасці манады. Гэта дазваляе вам парушаць розумы людзей!

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

666
20 апр. адказ дадзены MathematicalOrchid 20 крас. 2012-04-20 14:26 '12 а 14:26 2012-04-20 14:26

Шчыра кажучы, насуперак агульнаму разуменню манадай, яны не маюць нічога агульнага з дзяржавай. Манады - гэта проста спосаб абгарнуць рэчы і даць метады для выканання аперацый з абгорнутымі матэрыяламі без іх разгортвання.

Напрыклад, вы можаце стварыць тып для пераносу іншага, у Haskell:

 data Wrapped a = Wrap a 

Каб абгарнуць матэрыял, мы вызначаем

 return :: a -> Wrapped a return x = Wrap x 

Каб выканаць аперацыі без разгортвання, скажам, у вас ёсць функцыя f :: a -> b , тады вы можаце зрабіць гэта, каб падняць гэтую функцыю, каб дзейнічаць на загорнутыя значэння:

 fmap :: (a -> b) -> (Wrapped a -> Wrapped b) fmap f (Wrap x) = Wrap (fx) 

Гэта пра ўсё, што трэба зразумець. Аднак аказваецца, што для гэтага ўздыму існуе больш агульная функцыя, якая роўная bind :

 bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b) bind f (Wrap x) = fx 

bind можа зрабіць трохі больш, чым fmap , але не наадварот. На самай справе, fmap можна вызначыць толькі ў тэрмінах bind і return . Такім чынам, пры вызначэнні манады .. вы паказваеце яе тып (тут гэта быў Wrapped a ), а затым кажаце, як працуюць яго аперацыі return і bind .

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

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

Што тычыцца разумення манадай, не турбуйцеся пра гэта занадта шмат. Чытайце пра іх, што вы лічыце цікавымі, і не хвалюйцеся, калі вы не адразу зразумееце. Тады проста апусканне на мове, такім як Haskell, - гэта шлях. Манады - адна з тых рэчаў, дзе разуменне пранікае ў ваш мозг практыкай, аднойчы вы проста разумееце, што разумееце іх.

171
16 сент. адказ дадзены Arnar 16 сент. 2008-09-16 15:26 '08 у 15:26 2008-09-16 15:26

Але, Вы маглі б прыдумаць Monads!

sigfpe кажа:

Але ўсе яны ўводзяць манады як нешта эзатэрычнае, якое патрабуе ў тлумачэнні. Але я хачу сцвярджаць, што яны ўвогуле не эзотеричны. На самай справе, сутыкнуўшыся з рознымі праблемамі ў функцыянальным праграмаванні, вы былі б непазбежна прыцягнуты да пэўных рашэнням, усе з якіх з'яўляюцца прыкладамі манадай. На самай справе, я спадзяюся, вы прымусіце іх прыдумляць іх цяпер, калі вы гэтага яшчэ не зрабілі. Гэта невялікі крок, каб заўважыць, што ўсе гэтыя рашэнні на самай справе з'яўляюцца адным і тым жа рашэннем у маскіроўцы. І пасля чытання гэтага, вы маглі б лепш зразумець іншыя дакументы на монадах, таму што вы даведаецеся ўсё, што бачыце, як нешта, што вы ўжо вынайшлі.

Многія з праблем, якія спрабуюць вырашыць манады, звязаныя з праблемай пабочных эфектаў. Таму мы пачнем з іх. (Звярніце ўвагу, што манады дазваляюць рабіць больш, чым апрацоўваць пабочныя эфекты, у прыватнасці, многія тыпы аб'ектаў-кантэйнераў можна разглядаць як манады. Некаторыя з ўвядзення ў манады цяжка змірыцца з гэтымі двума рознымі відамі выкарыстання манадай і засяродзіцца толькі на адным ці іншай. )

У імператыўным мове праграмавання, такім як З ++, функцыі не вядуць сябе як функцыі матэматыкі. Напрыклад, выкажам здагадку, што ў нас ёсць функцыя З ++, якая прымае адзін аргумент з якая плавае коскі і вяртае вынік з якая плавае коскі. Павярхоўна гэта можа здацца трохі падобным на адлюстраванне матэматычнай функцыі на reals, але функцыя З ++ можа зрабіць больш, чым проста вярнуць лік, якое залежыць ад яго аргументаў. Ён можа счытваць і запісваць значэння глабальных зменных, а таксама запісваць выходныя дадзеныя на экран і атрымліваць ўваходныя дадзеныя ад карыстальніка. Аднак на чыстым функцыянальным мове функцыя можа чытаць толькі тое, што ёй прадастаўляецца ў сваіх аргументах, і адзіны спосаб, якім гэта можа паўплываць на свет, - гэта вярнуць значэння.

162
05 авг. адказ дадзены nlucaroni 05 жнів. 2008-08-05 19:28 '08 у 19:28 2008-08-05 19:28

Монада - гэта тып дадзеных, які мае дзве аперацыі: >>= (AKA bind ) і return (AKA unit ). return прымае адвольнае значэнне і стварае асобнік манады з ім. >>= бярэ асобнік манады і адлюстроўвае над ёй функцыю. (Вы можаце бачыць, што монада - гэта дзіўны тып дадзеных, паколькі на большасці моў праграмавання вы не маглі напісаць функцыю, якая прымае адвольнае значэнне і стварае з яе тып. Манадай выкарыстоўваюць выгляд параметрычны палімарфізм .)

У пазначэнні Haskell інтэрфейс манады запісваецца

 class Monad m where return :: a -> ma (>>=) :: forall ab . ma -> (a -> mb) -> mb 

Гэтыя аперацыі павінны падпарадкоўвацца пэўным "законах", але гэта не мае асаблівага значэння: "законы" проста кадыфікуецца спосаб, якім павінны паводзіць сябе разумныя рэалізацыі аперацый (у асноўным, што >>= і return павінны пагадзіцца аб тым, як значэння пераўтворацца ў асобнікі манады і што >>= з'яўляецца асацыятыўным).

Манады - гэта не толькі стан і ўвод-выснова: яны абстрагуюцца агульную схему вылічэнняў, якая ўключае ў сябе працу з станам, ўводу-вываду, выключэннямі і недетерминированностью. Верагодна, найпростымі манадай для разумення з'яўляюцца спісы і тыпы опцый:

 instance Monad [ ] where [] >>= k = [] (x:xs) >>= k = kx ++ (xs >>= k) return x = [x] instance Monad Maybe where Just x >>= k = kx Nothing >>= k = Nothing return x = Just x 

дзе [] і : - канструктары спісу, ++ - аператар канкатэнацыі, а Just і Nothing - канструктары Maybe . Абедзве гэтыя манады Інкапсулюйце агульныя і карысныя шаблоны вылічэнняў па іх адпаведным тыпам дадзеных (звярніце ўвагу, што ні адно з іх не мае нічога агульнага з пабочнымі эфектамі або ўводу-высновамі).

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

80
05 сент. адказ дадзены Chris Conway 05 сент. 2008-09-05 05:50 '08 у 05:50 2008-09-05 05:50

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

Функцыя больш высокага парадку - гэта проста функцыя, якая прымае функцыю ў якасці аргументу.

Функтором з'яўляецца любая канструкцыя тыпу T для якой існуе функцыя больш высокага парадку, назавем яе map , якая пераўтворыць функцыю тыпу a → b (зададзеную любымі двума тыпамі a і b ) у функцыю T a → T b . Гэта функцыя map таксама павінна падпарадкоўвацца законам ідэнтычнасці і кампазіцыі такім чынам, што наступныя выразы вяртаюць true для ўсіх p і q (абазначэнне Haskell):

 map id = id map (p . q) = map p . map q 

Напрыклад, канструктар тыпу List з'яўляецца функтором, калі ён абсталяваны функцыяй тыпу (a → b) → List a → List b якая падпарадкоўваецца законам вышэй. Адзіная практычная рэалізацыя відавочная. Атрыманая ў выніку функцыя List a → List b выконвае ітэрацыю па дадзенага спісу, выклікаючы (a → b) для кожнага элемента і вяртае спіс вынікаў.

Монада з'яўляецца па сутнасці проста функтором T з двума дадатковымі метадамі, join тып T (T a) → T a і unit (часам званую return , fork або pure ) тыпу a → T a . Для спісаў у Haskell:

 join :: [[a]] -> [a] pure :: a -> [a] 

Чаму гэта карысна? Так як вы можаце, напрыклад, map спіс з функцыяй, якая вяртае спіс. Join прымае спіс спісаў і аб'ядноўвае іх. List - монада, таму што гэта магчыма.

Вы можаце напісаць функцыю, якая map , а затым join . Гэтая функцыя называецца bind , або flatMap , або (>>=) , або (=<<) . Звычайна гэта як асобнік манады даецца ў Haskell.

Монада павінна задавальняць пэўных законах, а менавіта, што join павінна быць асацыятыўным. Гэта азначае, што калі ў вас ёсць значэнне x тыпу [[[a]]] тады join (join x) павінен раўняцца join (map join x) . І pure павінна быць тоеснасці для join такіх, што join (pure x) == x .

73
ответ дан Apocalisp 27 сент. '08 в 9:36 2008-09-27 09:36