Come praticante, perché dovrei preoccuparmi di Haskell? Cos'è una monade e perché ne ho bisogno? [chiuso]

9

Semplicemente non capisco quale problema risolvono.

    
posta Job 22.01.2011 - 04:27
fonte

6 risposte

34

Le monadi non sono né buone né cattive. Sono solo Sono strumenti che vengono utilizzati per risolvere problemi come molti altri costrutti di linguaggi di programmazione. Una loro applicazione molto importante è semplificare la vita dei programmatori che lavorano in un linguaggio puramente funzionale. Ma sono utili nei linguaggi non funzionali; è solo che le persone raramente si rendono conto che stanno usando una Monade.

Che cos'è una Monade? Il modo migliore di pensare a una Monade è come un modello di design. Nel caso di I / O, potresti probabilmente pensare che sia poco più di una pipeline glorificata in cui lo stato globale è ciò che viene passato tra le fasi.

Ad esempio, prendiamo il codice che stai scrivendo:

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

C'è molto di più in corso qui che soddisfa l'occhio. Ad esempio, noterai che putStrLn ha la seguente firma: putStrLn :: String -> IO () . Perché è questo?

Pensaci in questo modo: facciamo finta (per semplicità) che stdout e stdin sono gli unici file che possiamo leggere e scrivere. In un linguaggio imperativo, questo non è un problema. Ma in un linguaggio funzionale, non puoi mutare lo stato globale. Una funzione è semplicemente qualcosa che accetta un valore (o valori) e restituisce un valore (o valori). Un modo per aggirare questo è usare lo stato globale come valore che viene passato dentro e fuori da ogni funzione. Quindi potresti tradurre la prima riga di codice in qualcosa del genere:

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

... e il compilatore saprebbe di stampare tutto ciò che viene aggiunto al secondo elemento di global_state . Ora non so voi, ma odio programmare in questo modo. Il modo in cui è stato reso più facile è stato usare Monads. In una Monade, si passa un valore che rappresenta una sorta di stato da un'azione all'altra. Ecco perché putStrLn ha un tipo di ritorno di IO () : restituisce il nuovo stato globale.

Allora perché ti importa? Bene, i vantaggi della programmazione funzionale rispetto al programma imperativo sono stati discussi a morte in diversi punti, quindi non ho intenzione di rispondere a questa domanda in generale (ma vedi questo documento se vuoi ascoltare il caso per la programmazione funzionale). Tuttavia, per questo specifico caso, potrebbe essere d'aiuto capire cosa sta provando Haskell.

Molti programmatori ritengono che Haskell cerchi di impedire loro di scrivere codice imperativo o utilizzare effetti collaterali. Non è del tutto vero. Pensaci in questo modo: un linguaggio imperativo è quello che consente gli effetti collaterali di default, ma ti permette di scrivere codice funzionale se vuoi davvero (e sei disposto a gestire alcune delle contorsioni che richiederebbero). Haskell è puramente funzionale per impostazione predefinita, ma ti consente di scrivere codice imperativo se lo desideri (cosa che faresti se il tuo programma dovesse essere utile). Il punto non è rendere difficile la scrittura di codice con effetti collaterali. È per assicurarsi che tu sia esplicito sull'avere effetti collaterali (con il sistema di tipi che impone questo).

    
risposta data 22.01.2011 - 07:05
fonte
7

Mi morderò !!! Le Monade da sole non sono davvero una ragion d'essere per Haskell (le prime versioni di Haskell non le avevano nemmeno).

La tua domanda è un po 'come dire "C ++ quando guardo la sintassi, mi annoio così tanto, ma i template sono una caratteristica altamente pubblicizzata del C ++ quindi ho visto un'implementazione in qualche altra lingua".

L'evoluzione di un programmatore Haskell è una battuta, non è pensata per essere presa sul serio.

Una Monade ai fini di un programma in Haskell è un'istanza della classe tipo Monad, vale a dire, è un tipo che capita di supportare un certo piccolo insieme di operazioni. Haskell ha un supporto speciale per i tipi che implementano la classe di tipo Monad, in particolare il supporto sintattico. In pratica ciò che ne risulta è quello che è stato definito "semi-colon programmabile". Quando si combina questa funzionalità con alcune delle altre funzionalità di Haskell (funzioni di prima classe, la pigrizia per impostazione predefinita) si ottiene la capacità di implementare determinate cose come librerie che sono state tradizionalmente considerate caratteristiche del linguaggio. Ad esempio, è possibile implementare un meccanismo di eccezione. È possibile implementare il supporto per le continuazioni e le coroutine come una libreria. Haskell, il linguaggio non supporta le variabili mutabili: utilizzando questa combinazione di funzionalità è possibile implementarle nuovamente come libreria.

Chiedete informazioni su "Forse / Identità / Monade di divisione sicura ???". The Maybe monad è un esempio di come si può implementare (molto semplice, solo un'eccezione) la gestione delle eccezioni come una libreria.

Hai ragione, scrivere messaggi e leggere l'input dell'utente non è molto particolare. IO è un pessimo esempio di "monade come caratteristica".

Ma per iterare, una "feature" di per sé (ad es. Monads) isolata dal resto del linguaggio non appare necessariamente utile (una nuova grande caratteristica di C ++ 0x è un riferimento a valore, non significa puoi toglierli dal contesto C ++ perché la sintassi ti annoia e devi necessariamente vedere l'utilità). Un linguaggio di programmazione non è qualcosa che si ottiene lanciando un sacco di funzionalità in un secchio.

    
risposta data 22.01.2011 - 05:19
fonte
4

I programmatori scrivono tutti i programmi, ma le somiglianze finiscono qui. Penso che i programmatori differiscano molto più di quanto la maggior parte dei programmatori possa immaginare. Prendi una "battaglia" di vecchia data, come la tipizzazione delle variabili statiche e i tipi solo di runtime, lo script rispetto alla compilazione, lo stile C rispetto agli oggetti. Troverai impossibile argomentare razionalmente che un campo è inferiore, perché alcuni di loro sfornano un codice eccellente in un sistema di programmazione che sembra inutile o addirittura addirittura inutilizzabile.

Penso che le persone diverse pensino in modo diverso, e se non sei tentato dallo zucchero sintattico o dalle astrazioni in particolare che esistono solo per convenienza e in realtà hanno un costo di runtime significativo, allora stai lontano da tali linguaggi.

Ti consiglio comunque di provare almeno a familiarizzare con i concetti che stai rinunciando. Non ho niente contro qualcuno che è cecamente pro-puro-C, finchè in realtà capisce qual è il grosso problema delle espressioni lambda. Sospetto che la maggior parte non diventerà immediatamente un fan, ma almeno sarà lì nella parte posteriore della loro mente quando troveranno il problema perfetto che cosa sarebbe stato oh molto più facile da risolvere con lambda.

E, soprattutto, cerca di evitare di essere infastidito dal parlare da fan, soprattutto da persone che non sanno realmente di cosa stanno parlando.

    
risposta data 22.01.2011 - 05:15
fonte
4

Haskell impone Trasparenza referenziale : dati gli stessi parametri, ogni funzione restituisce sempre lo stesso risultato, no importa quante volte chiami quella funzione.

Ciò significa, ad esempio, che su Haskell (e senza Monads) non è possibile implementare un generatore di numeri casuali. In C ++ o Java puoi farlo usando variabili globali, memorizzando il valore intermedio "seme" del generatore casuale.

Su Haskell la controparte delle variabili globali sono Monade.

    
risposta data 22.01.2011 - 15:59
fonte
4

Una vecchia domanda, ma è davvero buona, quindi risponderò.

Puoi pensare alle monadi come blocchi di codice per i quali hai il controllo completo su come vengono eseguiti: cosa deve restituire ciascuna riga di codice, se l'esecuzione dovrebbe fermarsi in qualsiasi momento, se tra un linea.

Darò alcuni esempi di cose che le monadi abiliteranno sarebbe altrimenti difficile. Nessuno di questi esempi è in Haskell, solo perché la mia conoscenza di Haskell è un po 'traballante, ma sono tutti esempi di come Haskell abbia ispirato l'uso delle monadi.

Parser

Normalmente, se volessi scrivere un parser di qualche tipo, ad esempio per implementare un linguaggio di programmazione, dovresti leggere il Specifica BNF e scrivi un bel po 'di codice loopy per analizzarlo, oppure dovresti usare un compilatore compilatore come Flex, Bison, yacc ecc. Ma con le monade, puoi creare una sorta di "parser del compilatore" proprio in Haskell.

I parser non possono essere realmente fatti senza le monadi o linguaggi speciali come yacc, bison ecc.

Ad esempio, ho preso le specifiche del linguaggio BNF per il protocollo 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 lo ha ridotto a circa 40 righe di codice in F # (che è un'altra lingua che supporta le monadi):

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

La sintassi di monad di F # è abbastanza brutta rispetto a quella di Haskell, e probabilmente avrei potuto migliorarla un po '- ma il punto da portare a casa è che strutturalmente, il codice parser è identico al BNF. Non solo questo avrebbe richiesto molto più lavoro senza monadi (o un generatore di parser), avrebbe avuto quasi nessuna somiglianza con le specifiche, e quindi sarebbe stato terribile sia da leggere che da mantenere.

Multitasking personalizzato

Normalmente, il multitasking è pensato come una funzionalità del sistema operativo, ma con le monadi è possibile scrivere il proprio programma di pianificazione in modo tale che dopo ogni istruzione monad, il programma passi il controllo all'utilità di pianificazione, che quindi sceglierà un'altra monade per eseguire.

Un ragazzo ha fatto un "compito" monad per controllare il gioco loop (di nuovo in F #), in modo che invece di dover scrivere tutto come una macchina a stati che agisca su ogni Update() chiamata, potrebbe semplicemente scrivere tutte le istruzioni come se fossero una singola funzione.

In altre parole, invece di dover fare qualcosa di simile:

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

Potresti fare qualcosa del tipo:

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

LINQ to SQL

LINQ to SQL è in realtà un esempio di monade e una funzionalità simile potrebbe essere facilmente implementata in Haskell.

Non entrerò nei dettagli poiché non ricordo esattamente tutto ciò, ma Erik Meijer lo spiega abbastanza bene .

    
risposta data 03.08.2011 - 00:39
fonte
1

Se hai familiarità con i pattern GoF, le monadi sono come il pattern Decorator e il pattern Builder messi insieme, con gli steroidi, morsi da un tasso radioattivo.

Ci sono risposte migliori sopra, ma alcuni dei benefici specifici che vedo sono:

  • le monadi decorano alcuni tipi di core con proprietà aggiuntive senza modificare il tipo di core. Ad esempio, una monade potrebbe "sollevare" String e aggiungere valori come "isWellFormed", "isProfanity" o "isPalindrome" ecc.

  • allo stesso modo, le monadi consentono di conglomerare un tipo semplice in un tipo di raccolta

  • le monadi consentono il binding tardivo delle funzioni in questo spazio di ordine superiore

  • le monadi consentono di combinare funzioni e argomenti arbitrari con un tipo di dati arbitrario, nello spazio di ordine superiore

  • le monadi consentono di fondere le funzioni pure e senza stato con una base impura e con stato, in modo da poter tenere traccia di dove si trova il problema

Un esempio familiare di una monade in Java è List. Ci vuole un po 'di core, come String, e lo "solleva" nello spazio monade di List, aggiungendo informazioni sulla lista. Quindi lega nuove funzioni in quello spazio come get (), getFirst (), add (), empty (), ecc.

Su larga scala, immagina che invece di scrivere un programma, hai appena scritto un grosso Builder (come il pattern GoF), e il metodo build () alla fine sputa fuori qualsiasi risposta che il programma avrebbe dovuto produrre. E che potresti aggiungere nuovi metodi al tuo ProgramBuilder senza dover ricompilare il codice originale. Ecco perché le monadi sono un modello di design potente.

    
risposta data 16.02.2014 - 08:32
fonte

Leggi altre domande sui tag