Как практик, почему я должен заботиться о Хаскеле? Что такое монада и зачем она мне? [закрыто]


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. Теперь я не знаю о вас, но я бы не хотел программировать так. То, как это было сделано проще - использовать монады. В монаде вы передаете значение, которое представляет какое-то состояние от одного действия к другому. Вот почему putStrLnимеет тип возврата IO (): он возвращает новое глобальное состояние.

Так почему тебя это волнует? Что ж, преимущества функционального программирования по сравнению с императивной программой были обсуждены до смерти в нескольких местах, поэтому я не собираюсь отвечать на этот вопрос в целом (но посмотрите эту статью, если вы хотите услышать случай для функционального программирования). Однако для этого конкретного случая может помочь, если вы поймете, чего пытается достичь Хаскелл.

Многие программисты считают, что Haskell пытается помешать им написать императивный код или использовать побочные эффекты. Это не совсем так. Подумайте об этом так: императивный язык - это язык, который допускает побочные эффекты по умолчанию, но позволяет вам писать функциональный код, если вы действительно этого хотите (и готовы иметь дело с некоторыми из требуемых искажений). По умолчанию Haskell является чисто функциональным, но позволяет вам писать императивный код, если вы действительно этого хотите (что вы делаете, если ваша программа должна быть полезной). Дело не в том, чтобы затруднить написание кода, который имеет побочные эффекты. Это чтобы убедиться, что у вас есть явные побочные эффекты (с помощью системы типов, обеспечивающей это).


6
Последний абзац - золото. Чтобы извлечь и перефразировать это немного: «Императивный язык - это язык, который позволяет побочные эффекты по умолчанию, но позволяет вам писать функциональный код, если вы действительно этого хотите. Функциональный язык является чисто функциональным по умолчанию, но позволяет вам писать императивный код если ты действительно хочешь ".
Фрэнк Шеарар

Стоит отметить, что статья, на которую вы ссылаетесь, в самом начале отвергает идею «неизменности как достоинства функционального программирования».
Мейсон Уилер

@MasonWheeler: я прочитал эти параграфы, не отвергая важность неизменяемости, но отвергая ее как убедительный аргумент для демонстрации превосходства функционального программирования. Фактически, он говорит то же самое об устранении goto(как аргумент для структурированного программирования) чуть позже в статье, характеризуя такие аргументы как «бесплодные». И все же никто из нас тайно не желает gotoвозвращения. Просто вы не можете утверждать, что gotoэто не нужно людям, которые широко его используют.
Роберт Харви

7

Я укушу !!! Монады сами по себе не являются смыслом существования Haskell (в ранних версиях Haskell их даже не было).

Ваш вопрос немного похож на высказывание «C ++, когда я смотрю на синтаксис, мне так скучно. Но шаблоны - это очень рекламируемая особенность C ++, поэтому я посмотрел на реализацию на каком-то другом языке».

Эволюция программиста на Haskell - это шутка, ее не следует воспринимать всерьез.

Monad для программы на Haskell - это экземпляр класса типов Monad, то есть тип, который поддерживает определенный небольшой набор операций. Haskell имеет специальную поддержку для типов, которые реализуют класс типов Monad, в частности, синтаксическую поддержку. Практически это приводит к тому, что называют «программируемой точкой с запятой». Когда вы объединяете эту функциональность с некоторыми другими функциями Haskell (первоклассные функции, по умолчанию лень), вы получаете возможность реализовать определенные вещи в виде библиотек, которые традиционно считались языковыми функциями. Например, вы можете реализовать механизм исключений. Вы можете реализовать поддержку продолжений и сопрограмм в виде библиотеки. Haskell, язык не поддерживает изменяемые переменные:

Вы спрашиваете о «Может быть / Личность / Безопасное разделение монад ???». Монада Maybe является примером того, как вы можете реализовать (очень простое, только одно исключение) обработку исключений в виде библиотеки.

Вы правы, написание сообщений и чтение пользовательского ввода не очень уникальны. IO - это паршивый пример «монад как особенности».

Но для повторения, одна «особенность» (например, монады) в отрыве от остальной части языка не обязательно сразу оказывается полезной (отличная новая особенность C ++ 0x - ссылки на rvalue, не означает, что вы можете использовать они вне контекста C ++, потому что это синтаксис утомляет вас и обязательно видит утилиту). Язык программирования - это не то, что вы получаете, бросая кучу функций в ведро.


На самом деле, haskell поддерживает изменяемые переменные через монаду ST (одна из немногих странных нечистых магических частей языка, играющих по своим собственным правилам).
Сара

4

Все программисты пишут программы, но на этом сходство заканчивается. Я думаю, что программисты отличаются гораздо больше, чем может себе представить большинство программистов. Возьмите любое давнее «сражение», например, статическая типизация переменных и типы, доступные только во время выполнения, создание сценариев и компиляция, стиль C и объектно-ориентированный. Вы обнаружите, что невозможно рационально утверждать, что один лагерь уступает, потому что некоторые из них производят отличный код в какой-то системе программирования, которая кажется мне бессмысленной или даже совершенно непригодной для использования.

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

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

И, прежде всего, старайтесь не раздражаться от разговоров фанатов, особенно от людей, которые на самом деле не знают, о чем говорят.


4

Haskell обеспечивает ссылочную прозрачность : при одинаковых параметрах каждая функция всегда возвращает один и тот же результат, независимо от того, сколько раз вы вызываете эту функцию.

Это означает, например, что на Haskell (и без Monads) вы не можете реализовать генератор случайных чисел. В C ++ или Java вы можете сделать это, используя глобальные переменные, сохраняя промежуточное «начальное» значение генератора случайных чисел.

На Haskell аналогом глобальных переменных являются монады.


Итак ... что если вы хотите генератор случайных чисел? Разве это не функция? Даже если нет, как я могу получить генератор случайных чисел?
Работа

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

В Haskell вы либо передаете «RandGen», который в основном является текущим состоянием ГСЧ. Таким образом, функция, которая генерирует новое случайное число, берет RandGen и возвращает кортеж с новым RandGen и произведенным числом. Альтернатива - указать где-нибудь, что вы хотите список случайных чисел от минимального до максимального значения. Это вернет ленивый бесконечный поток чисел, поэтому мы можем просто пройтись по этому списку, когда нам понадобится новое случайное число.
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 # довольно безобразен по сравнению с синтаксисом в Haskell, и я, вероятно, мог бы немного его улучшить, но главное - структурно, код синтаксического анализатора идентичен 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 to SQL

LINQ to SQL на самом деле является примером монады, и аналогичные функциональные возможности могут быть легко реализованы в Haskell.

Я не буду вдаваться в подробности, так как не все точно помню, но Эрик Мейер объясняет это довольно хорошо .


1

Если вы знакомы с паттернами GoF, монады похожи на паттерн Decorator и паттерн Builder, составленный вместе на стероидах, укушенный радиоактивным барсуком.

Выше приведены лучшие ответы, но я вижу следующие конкретные преимущества:

  • монады украшают некоторый тип ядра дополнительными свойствами без изменения типа ядра. Например, монада может «поднять» строку и добавить значения, такие как «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.