Як практикуючий, чому я повинен дбати про Haskell? Що таке монада і для чого вона потрібна? [зачинено]


9

Я просто не розумію, яку проблему вони вирішують.



2
Я думаю, що це редагування трохи екстремальне. Я думаю, що ваше питання по суті було гарним. Просто деякі його частини були трохи ... аргументованими. Це, мабуть, лише результат розчарування, намагаючись навчитися чогось, у чому ви просто не бачили сенсу.
Джейсон Бейкер

@SnOrfus, я був тим, хто надумав це питання. Я був лінивий, щоб правильно це відредагувати.
Робота

Відповіді:


34

Монади не є ні добрими, ні поганими. Вони просто є. Це інструменти, які використовуються для вирішення проблем, як і багато інших конструкцій мов програмування. Одне дуже важливе їх застосування - це полегшити життя програмістам, які працюють чисто функціональною мовою. Але вони корисні в нефункціональних мовах; це просто те, що люди рідко розуміють, що використовують монаду.

Що таке Монада? Найкращий спосіб подумати над Монадою - це модель дизайну. Що стосується вводу-виводу, то, ймовірно, ви могли б вважати це лише трохи більше ніж прославленим трубопроводом, де глобальна держава - це те, що проходить між етапами.

Наприклад, візьмемо код, який ви пишете:

do
  putStrLn "What is your name?"
  name <- getLine
  putStrLn ("Nice to meet you, " ++ name ++ "!")

Тут відбувається набагато більше, ніж очі. Наприклад, ви помітите , що putStrLnмає такий підпис: putStrLn :: String -> IO (). Чому це?

Подумайте про це так: давайте зробимо вигляд, що (для простоти), що stdout і stdin - це єдині файли, до яких ми можемо читати і записувати. Імперативною мовою це не проблема. Але функціональною мовою ви не можете мутувати глобальний стан. Функція - це просто те, що приймає значення (або значення) і повертає значення (або значення). Одним із способів цього є використання глобального стану як значення, що передається у та виходить із кожної функції. Таким чином, ви могли перекласти перший рядок коду приблизно так:

global_state <- (\(stdin, stdout) -> (stdin, stdout ++ "What is your name?")) global_state

... і компілятор знав би надрукувати все, що додано до другого елемента global_state. Зараз я не знаю про вас, але мені б не хотілося так програмувати. Шляхом цього стало простіше використовувати Monads. У монаді ви передаєте значення, яке представляє певний стан від однієї дії до іншої. Ось чому putStrLnє тип повернення IO (): це повернення нового глобального стану.

То чому ти дбаєш? Ну, переваги функціонального програмування над імперативною програмою були обговорені на смерть у кількох місцях, тому я взагалі не збираюся відповідати на це питання (але дивіться цю статтю, якщо ви хочете почути справу щодо функціонального програмування). Хоча для цього конкретного випадку, це може допомогти, якщо ви зрозуміли, що Haskell намагається досягти.

Дуже багато програмістів вважають, що Haskell намагається завадити їм написати імперативний код або використовувати побічні ефекти. Це не зовсім так. Подумайте про це так: імперативна мова - це мова, яка дозволяє за замовчуванням використовувати побічні ефекти, але дозволяє писати функціональний код, якщо ви дійсно хочете (і готові мати справу з деякими вимогами, які потребують). За замовчуванням Haskell є чисто функціональним, але дозволяє писати імперативний код, якщо ви дійсно хочете (що ви робите, якщо ваша програма буде корисною). Сенс не в тому, щоб складно писати код із побічними ефектами. Потрібно переконатися, що ви маєте явну думку про наявність побічних ефектів (із типом системи, яка це виконує).


6
Останній абзац - золото. Щоб витягнути і перефразовувати це трохи: "Імперативна мова - це така, яка дозволяє за замовчуванням використовувати побічні ефекти, але дозволяє писати функціональний код, якщо ви цього дійсно хочете. Функціональна мова за замовчуванням є чисто функціональною, але дозволяє писати імперативний код якщо ти справді хочеш цього ».
Френк Ширар

Варто зазначити, що документ, до якого ви посилаєтесь, конкретно відкидає ідею "незмінність як добродію функціонального програмування" на самому початку.
Мейсон Уілер

@MasonWheeler: Я читав ці параграфи, не як відкидаючи важливість незмінності, але відкидаючи це як переконливий аргумент для демонстрації переваги функціонального програмування. Справді, він говорить те саме про усунення goto(як аргумент для структурованого програмування) трохи пізніше у статті, характеризуючи такі аргументи як "безрезультатні". І все ж ніхто з нас таємно не бажає gotoповернення. Просто не можна стверджувати, що gotoце не потрібно людям, які широко використовують його.
Роберт Харві

7

Я кусаю !!! Самі по собі монади насправді не є причиною існування для Haskell (ранні версії Haskell навіть не мали їх).

Ваше запитання дещо нагадує "C ++, коли я дивлюся на синтаксис, мені так нудно. Але шаблони - це дуже рекламована функція C ++, тому я подивився на реалізацію якоюсь іншою мовою".

Еволюція програміста Haskell - це жарт, це не призначено сприймати серйозно.

Монада для програми програми в Haskell - це екземпляр класу типу Monad, тобто тип, який відбувається для підтримки певного невеликого набору операцій. Haskell має спеціальну підтримку типів, що реалізують клас типу Monad, зокрема синтаксичну підтримку. Практично, це призводить до того, що було позначено як "програмована напівкрапка". Якщо ви поєднуєте цю функціональність з деякими іншими функціями Haskell (першокласні функції, лінь за замовчуванням), ви отримуєте можливість реалізувати певні речі як бібліотеки, які традиційно вважаються мовними особливостями. Наприклад, ви можете застосувати механізм винятку. Ви можете реалізовувати підтримку для продовження та розробки як бібліотека. Haskell, мова не підтримує змінні змінні:

Ви запитуєте про "Можливо / Ідентичність / Безпечний монад ???". Монада "Можливо" - це приклад того, як ви можете реалізувати (дуже простий, лише один виняток) обробку виключень як бібліотеку.

Ви маєте рацію, написання повідомлень та читання вводу користувача не дуже унікальне. IO - хитрий приклад "монади як особливості".

Але, щоб повторити, одна "особливість" сама по собі (наприклад, Monads) у відриві від решти мови не обов'язково одразу здається корисною (чудова нова функція C ++ 0x - це рецензуючі посилання, не означає, що ви можете прийняти їх поза контекстом C ++, оскільки це синтаксис вас нудить і обов'язково бачать утиліту). Мова програмування - це не те, що ви отримуєте, кидаючи купу функцій у відро.


Насправді, haskell має підтримку змінних змінних через монаду ST (одна з небагатьох дивних нечистих магічних частин мови, яка грає за власними правилами).
сара

4

Усі програмісти пишуть програми, але подібність закінчується. Я думаю, що програмісти відрізняються набагато більше, ніж може уявити більшість програмістів. Візьміть будь-яку давню "битву", як-от статична змінна введення проти типів лише для виконання, сценарій проти складеного, С-стиль та об'єктно-орієнтований. Раціонально стверджувати, що один табір є неповноцінним, вам неможливо, тому що деякі з них створюють чудовий код у певній системі програмування, яка здається мені безглуздою або навіть прямо непридатною.

Я думаю, що різні люди просто думають по-іншому, і якщо вас не спокушає синтаксичний цукор або особливо абстракції, які існують лише для зручності і насправді мають значну вартість виконання, то будь-яким чином тримайтеся подалі від таких мов.

Однак я б рекомендував вам хоча б спробувати ознайомитись з поняттями, від яких ви відмовляєтесь. Я не маю нічого проти того, хто є злісним про-чистим-С, якщо вони насправді розуміють, яка велика справа в лямбдаських виразах. Я підозрюю, що більшість не одразу стане фанатом, але, принаймні, це буде там, де вони знаходять ідеальну проблему, що було б так легше вирішити з лямбдами.

І, перш за все, намагайтеся уникати роздратування фанбойських розмов, особливо людей, які насправді не знають, про що говорять.


4

Haskell застосовує референтну прозорість : даючи однакові параметри, кожна функція завжди повертає один і той же результат, незалежно від того, скільки разів ви викликаєте цю функцію.

Це означає, наприклад, що в Haskell (і без Monads) ви не можете реалізувати генератор випадкових чисел. У C ++ або Java ви можете це зробити, використовуючи глобальні змінні, зберігаючи проміжне значення "насіння" випадкового генератора.

У Haskell аналогом глобальних змінних є Monads.


Отже ... що робити, якщо ви хотіли генератора випадкових чисел? Хіба це не функція? Навіть якщо ні, як я можу отримати генератор випадкових чисел?
робота

@Job Ви можете зробити генератор випадкових чисел всередині монади (в основному це трекер стану), або ви можете використовувати unsafePerformIO, чорт Хаскелл, який ніколи не повинен використовуватися (і справді, можливо, порушить вашу програму, якщо ви будете використовувати випадковість всередині нього!)
альтернатива

У Haskell ви або проїжджаєте навколо "RandGen", який в основному є поточним станом RNG. Таким чином, функція, яка генерує нове випадкове число, приймає RandGen і повертає кортеж з новим RandGen та отриманим числом. Альтернативою є десь вказати, що потрібно список випадкових чисел між min та max значенням. Це поверне ліниво оцінений нескінченний потік чисел, тому ми можемо просто пройти цей список, коли нам потрібно нове випадкове число.
Qqwy

Так само, як ви отримуєте їх будь-якою іншою мовою! ви отримуєте деякий алгоритм генератора псевдовипадкових чисел, а потім ви засіваєте його з деяким значенням і збираєте "випадкові" числа, що вискакують! Єдина відмінність полягає в тому, що такі мови, як C # і Java, автоматично насівають PRNG, використовуючи системний годинник або подібні речі. Це і той факт, що в haskell ви також отримуєте новий PRNG, який ви можете використовувати для отримання "наступного" номера, тоді як у C # / Java це все робиться внутрішньо, використовуючи змінні змінні в Randomоб'єкті.
сара

4

Вигляд старого питання, але він справді хороший, тому я відповім.

Ви можете розглядати монади як блоки коду, за якими ви маєте повний контроль над тим, як вони виконуються: що повинен повертати кожен рядок коду, чи слід зупиняти виконання в будь-якій точці, чи повинна відбуватися якась інша обробка між кожним рядком.

Я наведу кілька прикладів того, що монади дозволяють, що в іншому випадку буде важко. Жоден із цих прикладів не є в Haskell, тільки тому, що мої знання Haskell трохи хиткі, але вони всі приклади того, як Haskell надихнув на використання монад.

Парсери

Зазвичай, якщо ви хотіли написати якийсь аналізатор, скажімо, реалізувати мову програмування, вам доведеться або прочитати специфікацію BNF і написати цілу купу кодового коду, щоб розібрати його, або вам доведеться використовувати компілятор-компілятор як Flex, Bison, yacc тощо. Але з монадами ви можете зробити своєрідний "аналізатор компілятора" прямо в Haskell.

Парсери справді не обійтися без монад чи мов спеціального призначення, таких як yacc, bison тощо.

Наприклад, я взяв специфікацію мови BNF для протоколу IRC :

message    =  [ ":" prefix SPACE ] command [ params ] crlf
prefix     =  servername / ( nickname [ [ "!" user ] "@" host ] )
command    =  1*letter / 3digit
params     =  *14( SPACE middle ) [ SPACE ":" trailing ]
           =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]

nospcrlfcl =  %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
                ; any octet except NUL, CR, LF, " " and ":"
middle     =  nospcrlfcl *( ":" / nospcrlfcl )
trailing   =  *( ":" / " " / nospcrlfcl )

SPACE      =  %x20        ; space character
crlf       =  %x0D %x0A   ; "carriage return" "linefeed"

І скоротив його приблизно до 40 рядків коду у F # (це ще одна мова, яка підтримує монади):

type UserIdentifier = { Name : string; User: string; Host: string }

type Message = { Prefix : UserIdentifier option; Command : string; Params : string list }

let space = character (char 0x20)

let parameters =
    let middle = parser {
        let! c = sat <| fun c -> c <> ':' && c <> (char 0x20)
        let! cs = many <| sat ((<>)(char 0x20))
        return (c::cs)
    }
    let trailing = many item
    let parameter = prefixed space ((prefixed (character ':') trailing) +++ middle)
    many parameter

let command = atLeastOne letter +++ (count 3 digit)

let prefix = parser {
    let! name = many <| sat (fun c -> c <> '!' && c <> '@' && c <> (char 0x20))   //this is more lenient than RFC2812 2.3.1
    let! uh = parser {
        let! user = maybe <| prefixed (character '!') (many <| sat (fun c -> c <> '@' && c <> (char 0x20)))
        let! host = maybe <| prefixed (character '@') (many <| sat ((<>) ' '))
        return (user, host)
    }
    let nullstr = function | Some([]) -> null | Some(s) -> charsString s | _ -> null
    return { Name = charsString name; User = nullstr (fst uh); Host = nullstr (snd uh) }
}

let message = parser {
    let! p = maybe (parser {
        let! _ = character ':'
        let! p = prefix
        let! _ = space
        return p
    })
    let! c = command
    let! ps = parameters
    return { Prefix = p; Command = charsString c; Params = List.map charsString ps }
}

Синтаксис монади F # є досить потворним порівняно з Хаскеллом, і я, швидше за все, міг би покращити це - але сенс взяти додому те, що структурно код парсера ідентичний BNF. Мало того, що це зайняло б набагато більше роботи без монад (або генератора аналізаторів), воно мало б майже не схоже на специфікацію, і, таким чином, було б страшно і читати, і підтримувати.

Спеціальна багатозадачність

Зазвичай багатозадачність вважається функцією ОС, але за допомогою монад ви можете написати власний планувальник таким чином, щоб після кожної монади інструкцій програма передавала контроль планувальнику, який вибирав би іншу монаду для виконання.

Один хлопець склав монаду "завдання" контролювати ігрові петлі (знову ж у F #), так що замість того, щоб писати все як державна машина, яка діє на кожен Update()дзвінок, він міг просто написати всі вказівки так, ніби вони були однією функцією .

Іншими словами, замість того, щоб робити щось на кшталт:

class Robot
{
   enum State { Walking, Shooting, Stopped }

   State state = State.Stopped;

   public void Update()
   {
      switch(state)
      {
         case State.Stopped:
            Walk();
            state = State.Walking;
            break;
         case State.Walking:
            if (enemyInSight)
            {
               Shoot();
               state = State.Shooting;
            }
            break;
      }
   }
}

Ви можете зробити щось на кшталт:

let robotActions = task {
   while (not enemyInSight) do
      Walk()
   while (enemyInSight) do
      Shoot()
}

LINQ в SQL

LINQ to SQL насправді є прикладом монади, і подібний функціонал може бути легко реалізований в Haskell.

Я не буду вникати в деталі, оскільки не пам'ятаю все так точно, але Ерік Мейєр це досить добре пояснює .


1

Якщо ви знайомі з моделями GoF, монади схожі на візерунок «Декоратор» та «Будівельник», складений разом, на стероїди, покусані радіоактивним барсуком.

Вгорі є кращі відповіді, але я бачу деякі конкретні переваги:

  • монади прикрашають деякий тип серцевини додатковими властивостями, не змінюючи тип серцевини. Наприклад, монада може "підняти" рядок і додати значення, такі як "isWellFormed", "isProfanity" або "isPalindrome" і т.д.

  • аналогічно, монади дозволяють конгломерувати простий тип у тип колекції

  • монади дозволяють пізнє прив'язування функцій до цього простору вищого порядку

  • монади дозволяють змішувати довільні функції та аргументи з довільним типом даних у просторі вищого порядку

  • монади дозволяють поєднувати чисті, бездержавні функції з нечистою, потужною основою, тож ви можете відслідковувати, де біда

Знайомий приклад монади на Java - List. Він займає деякий основний клас, наприклад String, і "піднімає" його в монадовий простір List, додаючи інформацію про список. Потім він прив'язує нові функції до цього простору, як get (), getFirst (), add (), empty () тощо.

У великому масштабі уявіть, що замість того, щоб написати програму, ви просто написали великий Builder (як шаблон GoF), а метод build () в кінці випромінював будь-яку відповідь, яку програма повинна була створити. І щоб ви могли додати нові методи до ProgramBuilder, не перекомпілюючи початковий код. Ось чому монади - це потужна модель дизайну.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.