Qual a importância dos conceitos avançados de Haskell, como Mônadas e Functors Aplicáveis, para a maioria das tarefas de programação de rotina?

16

Eu li o livro Learn You a Haskell até o ponto em que eles apresentam mônadas e coisas como Just a. Isso é tão contra-intuitivo para mim que sinto vontade de desistir de aprender.

Mas eu realmente quero tentar.

Gostaria de saber se posso ao menos evitar os conceitos avançados por um tempo e apenas começar a usar as outras partes da linguagem para realizar muitas tarefas práticas com as partes mais básicas do Haskell, como funções, IO padrão, manipulação de arquivos, interface de banco de dados e similares.

Essa é uma abordagem razoável para aprender a usar o Haskell?

dan
fonte
2
Não sei até onde você chegará. Sem mônadas, você não pode fazer nada útil no Haskell, já que fazer algo útil na programação (como escrever um programa sério que alguém realmente gostaria de usar) requer E / S e estado, os quais só podem ser gerenciados no Haskell através do uso de mônadas.
Mason Wheeler
1
@dan - eu finalmente entendi mônadas de Haskell lendo dois documentos - "Você poderia ter inventado mônadas" e "Enfrentando o esquadrão estranho". Não sei dizer se são melhores do que "Learn You a Haskell", que não li, mas funcionaram para mim. Para usar a notação de doação com a mônada de IO, você pode entender muito pouco o que são mônadas - você fará algumas coisas que farão os especialistas rir ou chorar, mas superficialmente, pelo menos, não é muito diferente de usar uma linguagem imperativa convencional. Mas é interessante obter algum entendimento mais profundo.
Steve314
2
@dan, levei quase uma década para me cansar do OOP e começar a apreciar / ter curiosidade sobre linguagens funcionais. Eu ainda aprenderia F # e Clojure antes de tentar Haskell com seriedade. Embora os desafios pessoais sejam bons, há algo a ser dito sobre seu instinto e o caminho de menor resistência. Depende muito de quem você é. Se você é do tipo fortemente matemático, provavelmente gostaria do Haskell / Schem desde o início. Se você é mais um cara 'faça isso', tudo bem também. Não se aproxime de Haskell com uma atitude de "faça ou morra". Continue aprendendo outras coisas e, quando entediado, volte.
Job
Obrigado pelo conselho @Job. Eu tentei o Clojure por um tempo, mas a sintaxe Lisp realmente não funciona para mim, tanto para leitura quanto para digitação. Acho a sintaxe de Haskell mais natural. Entretanto, estou feliz em usar o Ruby para projetos, mas espero começar a fazer alguns projetos práticos em Haskell.
dan
@dan, Engraçado, acho a sintaxe de Clojure bastante amigável, mas é provavelmente porque eu já havia sido exposto ao Scheme antes. Talvez depois do F # eu me acostumei com a maneira como Haskell faz as coisas.
Job

Respostas:

16

Vamos primeiro distinguir entre aprender os conceitos abstratos e aprender exemplos específicos deles.

Você não vai muito longe ignorando todos os exemplos específicos, pela simples razão de que eles são totalmente onipresentes. De fato, as abstrações existem em grande parte porque unificam as coisas que você faria de qualquer maneira com os exemplos específicos.

As abstrações em si, por outro lado, são certamente úteis , mas não são imediatamente necessárias. Você pode ir longe ignorando completamente as abstrações e apenas usando os vários tipos diretamente. Você vai querer entendê-los eventualmente, mas sempre poderá voltar depois. Na verdade, eu posso quase garantir que, se você fizer isso, quando voltar a fazê-lo, dará um tapa na testa e se perguntará por que passou todo esse tempo fazendo as coisas da maneira mais difícil, em vez de usar as convenientes ferramentas de uso geral.

Tome Maybe acomo exemplo. É apenas um tipo de dados:

data Maybe a = Just a | Nothing

É tudo menos auto-documentado; é um valor opcional. Ou você tem "apenas" algo do tipo aou não tem nada. Digamos que você tenha uma função de pesquisa de algum tipo, que retorne Maybe Stringpara representar a pesquisa de um Stringvalor que pode não estar presente. Então, você combina com o valor para ver qual é:

case lookupFunc key of
    Just val -> ...
    Nothing  -> ...

Isso é tudo!

Realmente, não há mais nada que você precise. Sem Functors ou Monads ou qualquer outra coisa. Eles expressam maneiras comuns de usar Maybe avalores ... mas são apenas expressões idiomáticas, "padrões de design", como você quiser chamar.

O único lugar em que você realmente não pode evitá-lo completamente é IO, mas de qualquer maneira é uma caixa preta misteriosa, então não vale a pena tentar entender o que isso significa como algo assim Monad.

De fato, aqui está uma folha de dicas para tudo o que você realmente precisa saber IOpor enquanto:

  • Se algo tem um tipo IO a, isso significa que é um procedimento que faz algo e cospe um avalor.

  • Quando você tem um bloco de código usando a donotação, escreva algo como isto:

    do -- ...
       inp <- getLine
       -- etc...
    

    ... significa executar o procedimento à direita do <-e atribuir o resultado ao nome à esquerda.

  • Considerando que se você tiver algo parecido com isto:

    do -- ...
       let x = [foo, bar]
       -- etc...
    

    ... significa atribuir o valor da expressão simples (não um procedimento) à direita da =e ao nome à esquerda.

  • Se você colocar algo lá sem atribuir um valor, assim:

    do putStrLn "blah blah, fishcakes"
    

    ... significa executar um procedimento e ignorar qualquer coisa que ele retorne. Alguns procedimentos têm o tipo IO ()- o ()tipo é um tipo de espaço reservado que não diz nada, de modo que apenas significa que o procedimento faz algo e não retorna um valor. Como uma voidfunção em outros idiomas.

  • Executar o mesmo procedimento mais de uma vez pode gerar resultados diferentes; esse é o tipo de ideia. É por isso que não há como "remover" o IOvalor de um valor, porque algo IOnão é um valor, é um procedimento para obter um valor.

  • A última linha de um dobloco deve ser um procedimento simples, sem atribuição, onde o valor de retorno desse procedimento se torna o valor de retorno para todo o bloco. Se você deseja que o valor de retorno use algum valor já atribuído, a returnfunção usa um valor simples e fornece um procedimento não operacional que retorna esse valor.

  • Fora isso, não há nada de especial IO; esses procedimentos são realmente valores simples, e você pode transmiti-los e combiná-los de maneiras diferentes. É somente quando eles são executados em um dobloco chamado em algum lugar mainque eles fazem qualquer coisa.

Então, em algo como este programa de exemplo totalmente chato e estereotipado:

hello = do putStrLn "What's your name?"
           name <- getLine
           let msg = "Hi, " ++ name ++ "!"
           putStrLn msg
           return name

... você pode lê-lo como um programa imperativo. Estamos definindo um procedimento chamado hello. Quando executado, primeiro ele executa um procedimento para imprimir uma mensagem perguntando seu nome; Em seguida, ele executa um procedimento que lê uma linha de entrada e atribui o resultado a name; depois atribui uma expressão ao nome msg; depois imprime a mensagem; em seguida, ele retorna o nome do usuário como resultado de todo o bloco. Como nameé a String, isso significa que helloé o procedimento que retorna a String, então ele tem o tipo IO String. E agora você pode executar esse procedimento em outro lugar, assim como ele é executado getLine.

Pfff, mônadas. Quem precisa deles?

CA McCann
fonte
2
@dan: De nada! Se você tiver alguma dúvida específica ao longo do caminho, não hesite em perguntar sobre o Stack Overflow. A tag [haskell] geralmente é bastante ativa e, se você explicar de onde vem, as pessoas são muito boas em ajudá-lo a entender, em vez de apenas dar respostas enigmáticas.
CA McCann
9

Qual a importância dos conceitos avançados de Haskell, como Mônadas e Functors Aplicáveis, para a maioria das tarefas de programação de rotina?

Eles são essenciais. Sem eles, é muito fácil usar Haskell como apenas outra linguagem imperativa e, embora Simon Peyton Jones tenha chamado Haskell, "a melhor linguagem de programação imperativa do mundo", você ainda está perdendo a melhor parte.

Mônadas, Transformadas de Mônada, Monoides, Functors, Functors Aplicable, Functors Contravarient, Arrows, Categories, etc, são padrões de design. Depois de encaixar a coisa específica que você está criando em uma delas, você pode recorrer a uma enorme variedade de funções que são escritas abstratamente. Essas classes de tipo não existem apenas para confundi-lo, mas para facilitar sua vida e torná-lo mais produtivo. Eles são, na minha opinião, a maior razão para a concisão do bom código Haskell.

dan_waterworth
fonte
1
+1: Obrigado por apontar que "Mônadas, Mônadas transformadas, Monóides, Functors, Functors aplicáveis, Functors contravarientes, setas, categorias etc. são padrões de design". !
Giorgio
7

É estritamente necessário se você apenas deseja que algo funcione? Não.

É necessário se você deseja escrever belos programas idiomáticos Haskell? Absolutamente.

Ao programar em Haskell, ajuda a manter duas coisas em mente:

  1. O sistema de tipos está lá para ajudá-lo. Se você compõe seu programa a partir de um código altamente polimórfico e pesado de tipografia, muitas vezes você pode entrar na maravilhosa situação em que o tipo de função desejada o guiará para a (uma única!) Implementação correta .

  2. As várias bibliotecas disponíveis foram desenvolvidas usando o princípio nº 1.

Então, ao escrever um programa em Haskell, começo escrevendo os tipos. Então começo de novo e escrevo alguns tipos mais gerais (e, se puderem ser instâncias de classes existentes, tanto melhor). Então escrevo algumas funções que me dão valores dos tipos que eu quero. (Então brinco com eles ghcie talvez escreva alguns testes de verificação rápida.) E, finalmente, colo tudo main.

É possível escrever Haskell feio que apenas faz com que algo funcione. Mas há muito mais a aprender quando você atinge esse estágio.

Boa sorte!

Lambdageek
fonte
1
Obrigado! Eu tenho oscilado entre Clojure e Haskell, mas agora estou inclinado a Haskell por causa de como pessoas prestativas como você têm sido.
dan
1
A comunidade Haskell parece ser muito agradável e útil. E parece que é de onde vêm muitas idéias interessantes.
Zachary K