Como profissional, por que devo me preocupar com Haskell? O que é uma mônada e por que eu preciso dela? [fechadas]


9

Eu simplesmente não entendo o problema que eles resolvem.



2
Eu acho que essa edição é um pouco extremada. Eu acho que sua pergunta foi essencialmente boa. Só que algumas partes eram um pouco ... argumentativas. O que provavelmente é apenas o resultado da frustração de tentar aprender algo que você simplesmente não estava entendendo.
Jason Baker

@SnOrfus, fui eu quem sacudiu a pergunta. Eu estava com preguiça de editá-lo corretamente.
Job

Respostas:


34

Mônadas não são boas nem ruins. Eles simplesmente são. São ferramentas usadas para resolver problemas, como muitas outras construções de linguagens de programação. Uma aplicação muito importante deles é facilitar a vida dos programadores que trabalham em uma linguagem puramente funcional. Mas eles são úteis em linguagens não funcionais; é que as pessoas raramente percebem que estão usando uma mônada.

O que é uma Mônada? A melhor maneira de pensar em uma Mônada é como um padrão de design. No caso de E / S, você provavelmente poderia pensar nisso como sendo pouco mais que um pipeline glorificado, onde o estado global é o que está sendo passado entre os estágios.

Por exemplo, vamos pegar o código que você está escrevendo:

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

Há muito mais acontecendo aqui do que aparenta. Por exemplo, você notará que putStrLntem a seguinte assinatura: putStrLn :: String -> IO (). Por que é isso?

Pense dessa maneira: vamos fingir (por uma questão de simplicidade) que stdout e stdin são os únicos arquivos nos quais podemos ler e escrever. Em uma linguagem imperativa, isso não é problema. Mas em uma linguagem funcional, você não pode alterar o estado global. Uma função é simplesmente algo que pega um valor (ou valores) e retorna um valor (ou valores). Uma maneira de contornar isso é usar o estado global como o valor que está sendo passado para dentro e para fora de cada função. Então você pode traduzir a primeira linha de código em algo como isto:

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

... e o compilador saberia imprimir qualquer coisa adicionada ao segundo elemento de global_state. Agora eu não sei sobre você, mas eu odiaria programar assim. A maneira como isso ficou mais fácil foi usar Mônadas. Em uma Mônada, você passa um valor que representa algum tipo de estado de uma ação para a seguinte. É por isso que putStrLntem um tipo de retorno IO (): está retornando o novo estado global.

Então, por que você se importa? Bem, as vantagens da programação funcional sobre o programa imperativo foram debatidas até o momento em vários lugares, então não vou responder a essa pergunta em geral (mas veja este artigo se você quiser ouvir o caso da programação funcional). Para este caso específico, porém, pode ajudar se você entender o que Haskell está tentando realizar.

Muitos programadores acham que Haskell tenta impedi-los de escrever códigos imperativos ou usar efeitos colaterais. Isso não é bem verdade. Pense desta maneira: uma linguagem imperativa é aquela que permite efeitos colaterais por padrão, mas permite escrever código funcional se você realmente quiser (e estiver disposto a lidar com algumas das contorções que exigiriam). Haskell é puramente funcional por padrão, mas permite que você escreva um código imprescindível se você realmente quiser (o que você faz se o seu programa for útil). A questão não é dificultar a escrita de códigos com efeitos colaterais. É para garantir que você seja explícito sobre ter efeitos colaterais (com o sistema de tipos que impõe isso).


6
Esse último parágrafo é ouro. Para extrair e parafrasear um pouco: "Uma linguagem imperativa é aquela que permite efeitos colaterais por padrão, mas permite escrever código funcional se você realmente quiser. Uma linguagem funcional é puramente funcional por padrão, mas permite que você escreva um código imperativo. se você realmente quiser. "
precisa saber é o seguinte

Vale a pena notar que o artigo ao qual você vinculou rejeita especificamente a idéia de "imutabilidade como virtude da programação funcional" logo no início.
Mason Wheeler

@MasonWheeler: Eu li esses parágrafos, não descartando a importância da imutabilidade, mas rejeitando-a como um argumento convincente para demonstrar a superioridade da programação funcional. De fato, ele diz o mesmo sobre a eliminação goto(como argumento para programação estruturada) um pouco mais tarde neste artigo, caracterizando argumentos como "infrutíferos". E, no entanto, nenhum de nós deseja secretamente gotoo retorno. É simplesmente que você não pode argumentar que gotonão é necessário para pessoas que o usam extensivamente.
Robert Harvey

7

Eu vou morder !!! Mônadas por si só não são realmente uma razão de ser para Haskell (as primeiras versões de Haskell nem as possuíam).

Sua pergunta é um pouco como dizer "C ++, quando olho para a sintaxe, fico tão entediado. Mas os modelos são um recurso altamente anunciado do C ++, então vi uma implementação em outra linguagem".

A evolução de um programador Haskell é uma piada, não deve ser levada a sério.

Uma Mônada para fins de um programa em Haskell é uma instância da classe de tipo Mônada, ou seja, é um tipo que suporta um determinado conjunto de operações pequeno. Haskell tem suporte especial para tipos que implementam a classe de tipo Mônada, especificamente suporte sintático. Praticamente o que isso resulta é o que foi chamado de "ponto e vírgula programável". Quando você combina essa funcionalidade com alguns dos outros recursos de Haskell (funções de primeira classe, preguiça por padrão), o que você obtém é a capacidade de implementar certas coisas como bibliotecas que tradicionalmente são consideradas recursos de linguagem. Você pode, por exemplo, implementar um mecanismo de exceção. Você pode implementar o suporte a continuações e corotinas como uma biblioteca. Haskell, o idioma não tem suporte para variáveis ​​mutáveis:

Você pergunta sobre "Mônadas Talvez / Identidade / Divisão Segura ???". A mônada Maybe é um exemplo de como você pode implementar (muito simples, apenas uma exceção) o tratamento de exceções como uma biblioteca.

Você está certo, escrever mensagens e ler a entrada do usuário não é muito exclusivo. IO é um péssimo exemplo de "mônadas como recurso".

Mas, para iterar, um "recurso" por si só (por exemplo, mônadas) isolado do resto do idioma não parece necessariamente imediatamente útil (um ótimo novo recurso do C ++ 0x são as referências de valor, não significa que você pode usar eles fora do contexto C ++ porque sua sintaxe o aborrece e necessariamente vê o utilitário). Uma linguagem de programação não é algo que você obtém jogando vários recursos em um balde.


Na verdade, o haskell tem suporte para variáveis ​​mutáveis ​​através da mônada ST (uma das poucas partes mágicas estranhas e impuras da linguagem que é reproduzida por suas próprias regras).
Sara

4

Todos os programadores escrevem programas, mas as semelhanças terminam aí. Eu acho que os programadores diferem muito mais do que a maioria dos programadores pode imaginar. Faça qualquer "batalha" de longa data, como digitação de variável estática versus tipos apenas de tempo de execução, script vs compilado, estilo C versus orientação a objeto. Você achará impossível argumentar racionalmente que um campo é inferior, porque alguns deles produzem código excelente em algum sistema de programação que parece inútil ou até totalmente inutilizável para mim.

Penso que pessoas diferentes pensam de maneira diferente, e se você não é tentado pelo açúcar sintático ou especialmente por abstrações que existem apenas por conveniência e que realmente têm um custo de execução significativo, fique longe de tais idiomas.

No entanto, eu recomendaria que você pelo menos tente se familiarizar com os conceitos que está desistindo. Não tenho nada contra alguém que é veementemente pró-puro-C, desde que eles realmente entendam qual é o grande problema das expressões lambda. Eu suspeito que a maioria não se tornará fã imediatamente, mas pelo menos estará lá no fundo de suas mentes quando encontrarem o problema perfeito, o que teria sido, oh, muito mais fácil de resolver com lambdas.

E, acima de tudo, tente evitar se incomodar com a fala dos fanboys, especialmente por pessoas que realmente não sabem do que estão falando.


4

Haskell reforça a transparência referencial : dados os mesmos parâmetros, todas as funções sempre retornam o mesmo resultado, não importa quantas vezes você chame essa função.

Isso significa, por exemplo, que no Haskell (e sem Mônadas) você não pode implementar um gerador de números aleatórios. Em C ++ ou Java, você pode fazer isso usando variáveis ​​globais, armazenando o valor intermediário "semente" do gerador aleatório.

Em Haskell, a contrapartida das variáveis ​​globais são as mônadas.


Então ... e se você quisesse um gerador de números aleatórios? Não é uma função também? Mesmo se não, como consigo um gerador de números aleatórios?
Job

@Job Você pode criar um gerador de números aleatórios dentro de uma mônada (é basicamente um rastreador de estado) ou pode usar o unsafePerformIO, o diabo de Haskell que nunca deve ser usado (e de fato provavelmente interromperá o seu programa se você usar aleatoriedade dentro dele!)
alternativa

Em Haskell, você passa um 'RandGen' que é basicamente o estado atual do RNG. Portanto, a função que gera um novo número aleatório pega um RandGen e retorna uma tupla com o novo RandGen e o número produzido. A alternativa é especificar em algum lugar que você deseja uma lista de números aleatórios entre um valor mínimo e um máximo. Isso retornará um fluxo infinito de números avaliados preguiçosamente, para que possamos percorrer esta lista sempre que precisarmos de um novo número aleatório.
Qqwy

Da mesma maneira que você os obtém em qualquer outro idioma! você se apodera de algum algoritmo gerador de números pseudo-aleatórios e, então, semeia-o com algum valor e colhe os números "aleatórios" que aparecem! A única diferença é que linguagens como C # e Java semeiam automaticamente o PRNG para você usando o relógio do sistema ou coisas assim. Isso e o fato de que no haskell você também obtém um novo PRNG que pode ser usado para obter o número "próximo", enquanto no C # / Java, tudo isso é feito internamente usando variáveis ​​mutáveis ​​no Randomobjeto.
Sara

4

É uma pergunta antiga, mas realmente boa, então eu responderei.

Você pode pensar nas mônadas como blocos de código para os quais você tem controle completo sobre como elas são executadas: o que cada linha de código deve retornar, se a execução deve parar a qualquer momento, se algum outro processamento deve ocorrer entre cada linha.

Vou dar alguns exemplos de coisas que as mônadas permitem que, caso contrário, seriam difíceis. Nenhum desses exemplos está em Haskell, apenas porque meu conhecimento sobre Haskell é um pouco instável, mas todos são exemplos de como Haskell inspirou o uso de mônadas.

Analisadores

Normalmente, se você quiser escrever um analisador de algum tipo, digamos, para implementar uma linguagem de programação, você precisará ler a especificação BNF e escrever um monte de código em loop para analisá-lo ou usar um compilador de compilador como Flex, Bison, yacc etc. Mas com mônadas, você pode criar um tipo de "analisador de compilador" direto no Haskell.

Analisadores não podem realmente ser feitos sem mônadas ou linguagens para fins especiais, como yacc, bison etc.

Por exemplo, peguei a especificação da linguagem BNF para o protocolo 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"

E reduziu para cerca de 40 linhas de código em F # (que é outra linguagem que suporta mônadas):

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 }
}

A sintaxe da mônada do F # é bastante feia comparada à de Haskell, e eu provavelmente poderia ter melhorado bastante isso - mas o ponto a ser levado para casa é que, estruturalmente, o código do analisador é idêntico ao BNF. Isso não apenas teria exigido muito mais trabalho sem mônadas (ou um gerador de analisador), como não teria quase nenhuma semelhança com a especificação e, portanto, seria terrível de ler e manter.

Multitarefa personalizada

Normalmente, a multitarefa é considerada um recurso do sistema operacional - mas com mônadas, você pode escrever seu próprio agendador de forma que, após cada mônada de instruções, o programa passe o controle para o agendador, que escolheria outra mônada para executar.

Um cara fez uma mônada de "tarefa" para controlar os loops de jogo (novamente em F #), para que, em vez de escrever tudo como uma máquina de estado que atua em cada Update()chamada, ele pudesse escrever todas as instruções como se fossem uma única função .

Em outras palavras, em vez de ter que fazer algo como:

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;
      }
   }
}

Você poderia fazer algo como:

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

LINQ para SQL

LINQ to SQL é realmente um exemplo de mônada, e funcionalidades semelhantes podem ser facilmente implementadas no Haskell.

Não vou entrar em detalhes, já que não me lembro de tudo com precisão, mas Erik Meijer explica isso muito bem .


1

Se você está familiarizado com os padrões GoF, as mônadas são como o padrão Decorator e o Builder juntos, em esteróides, picados por um texugo radioativo.

Existem respostas melhores acima, mas alguns dos benefícios específicos que vejo são:

  • as mônadas decoram algum tipo de núcleo com propriedades adicionais sem alterar o tipo de núcleo. Por exemplo, uma mônada pode "levantar" String e adicionar valores como "isWellFormed", "isProfanity" ou "isPalindrome" etc.

  • da mesma forma, as mônadas permitem agrupar um tipo simples em um tipo de coleção

  • mônadas permitem ligação tardia de funções neste espaço de ordem superior

  • mônadas permitem misturar funções e argumentos arbitrários com um tipo de dados arbitrário, no espaço de ordem superior

  • as mônadas permitem combinar funções puras e sem estado com uma base impura e com estado, para que você possa acompanhar onde está o problema

Um exemplo familiar de mônada em Java é List. Ele pega alguma classe principal, como String, e a "eleva" ao espaço de mônada da Lista, adicionando informações sobre a lista. Em seguida, vincula novas funções a esse espaço, como get (), getFirst (), add (), empty () etc.

Em grande escala, imagine que, em vez de escrever um programa, você acabou de escrever um grande Builder (como o padrão GoF), e o método build () no final cuspisse qualquer resposta que o programa deveria produzir. E que você pode adicionar novos métodos ao seu ProgramBuilder sem recompilar o código original. É por isso que as mônadas são um poderoso modelo de design.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.