2009-04-27 11 views
8

Sono veramente nuovo a Haskell (In realtà ho visto "Real World Haskell" da O'Reilly e ho pensato "Hmm, penso che imparerò programmazione funzionale" ieri) e mi chiedo: io è possibile utilizzare l'operatore costrutto per aggiungere un elemento all'inizio di una lista:Haskell Contro Operator (:)

1 : [2,3] 
[1,2,3] 

ho provato a fare un esempio tipo di dati che ho trovato nel libro e poi giocare con esso:

--in a file 
data BillingInfo = CreditCard Int String String 
| CashOnDelivery 
| Invoice Int 
deriving (Show) 

--in ghci 
$ let order_list = [Invoice 2345] 
$ order_list 
[Invoice 2345] 
$ let order_list = CashOnDelivery : order_list 
$ order_list 
[CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, ...- 

etc. .. si ripete per sempre, è questo perché usa la valutazione pigro?

- EDIT -

va bene, quindi viene martellato in testa che lascia order_list = CashOnDelivery: order_list non aggiunge CashOnDelivery al order_list originale e quindi impostare il risultato di order_list, ma invece è ricorsivo e crea una lista infinita, aggiungendo per sempre CashOnDelivery all'inizio di se stesso. Naturalmente ora ricordo che Haskell è un linguaggio funzionale e non posso cambiare il valore dell'originale order_list, quindi cosa dovrei fare per un semplice "tack alla fine (o all'inizio, qualunque cosa) di questa lista?" Crea una funzione che accetta un elenco e BillingInfo come argomenti, quindi restituisce un elenco?

- EDIT 2-

bene, sulla base di tutte le risposte che sto ricevendo e la mancanza di essere in grado di passare un oggetto per riferimento e mutare le variabili (come sono abituato a) ... Penso di aver appena fatto questa domanda in modo prematuro e che ho davvero bisogno di approfondire ulteriormente il paradigma funzionale prima che possa aspettarmi di capire davvero le risposte alle mie domande ... Immagino che quello che stavo cercando fosse come scrivere una funzione o qualcosa, prendere una lista e un oggetto e restituire una lista con lo stesso nome in modo che la funzione possa essere chiamata più di una volta, senza cambiare il nome ogni volta (come se fosse in realtà un programma che aggiungerebbe ordina a una lista ordini e l'utente non dovrebbe pensare di un nuovo nome per la lista ogni volta, ma piuttosto aggiungere un elemento alla stessa lista).

+0

Si creerà sempre un nuovo elenco. Perché vorresti usare il vecchio nome per fare riferimento a una nuova lista? – ephemient

+4

Prima di poter capire la risposta, devi prima capire la tua domanda. –

+2

lol, che profondità: P –

risposta

14

A tale scopo:

$ let order_list = [Invoice 2345] 
$ let order_list = CashOnDelivery : order_list 

La cosa importante da notare qui è che siete non solo l'aggiunta di un elemento CashOnDelivery al tuo primo order_list. Stai definendo una nuova variabile order_list che non ha nulla a che fare con la prima. Questa è una definizione ricorsiva, lo order_list sul lato destro si riferisce allo order_list che si sta definendo a sinistra, non a quello definito nella riga precedente. A causa di questa ricorsione ottieni una lista infinita.

ho il sospetto si voleva davvero fare qualcosa di simile:

$ let order_list = [Invoice 2345] 
$ order_list 
[Invoice 2345] 
$ let order_list2 = CashOnDelivery : order_list 
$ order_list2 
[CashOnDelivery, Invoice 2345] 
1

Il cdr degli svantaggi che hai appena creato fa riferimento a se stesso: la definizione di order_list utilizzata nel secondo let è la definizione che si sta creando. Utilizzare un nome di variabile diverso per aggirare completamente il problema di ricorsione, e il codice sarà anche meno confuso.

EDIT: Dopo aver letto la risposta di Joel, sembra che io sto parlando con un Lisp qui. Valutazione pigra o no, in ogni caso hai creato una definizione ricorsiva ...

1

Haskell utilizza la valutazione lenta ... niente viene valutato finché non è necessario, ecco perché order_list è memorizzato come un contro contenente CashOnDelivery e un altro, cellula di riferimento non valutata elenco degli ordini di nuovo.

0

Penso che tu intenda "è questo perché utilizza la valutazione pigro"? La risposta è sì:

let ol = CashOnDelivery : ol 

Questo ci dice che ol contiene l'elemento CashOnDelivery, e quindi il risultato del ol espressione. Questa espressione non viene valutata fino al necessario (quindi: pigrizia). Quindi, quando ol viene stampato, CashOnDelivery verrà stampato per primo. Solo dopo il verrà determinato il prossimo elemento dell'elenco, determinando quindi un comportamento infinito.

2

Sì, si sta tentando di stampare un elenco infinito che è possibile creare con una valutazione lazy. Per esempio

let a = 1 : a 

Crea una lista infinita di quelli, e si può prendere come molti di loro come si desidera con la funzione di ripresa, o quando si cerca di stamparlo. Si noti che si utilizza lo stesso identificatore sui lati sinistro e destro dell'equazione, e funziona: order_list è CashOnDelivery: order_list, ora sostituendo: order_list = CashOnConsegna: (CashOnDelivery: order_list) = Cash ... ecc.

Se si desidera creare un elenco [Cash ..., Invoice], non riutilizzare nomi del genere.

0

X: L significa "Crea una lista che inizia con X seguita dalle voci nell'elenco definito da L."

Si sta quindi definendo order_list come CashOnDelivery seguito dagli elementi nell'elenco definito order_list. Questa definizione è ricorsiva, motivo per cui la valutazione dell'elenco continua a restituire CashOnDelivery. L'elenco contiene in realtà un numero infinito di valori CashOnDelivery seguito da da un singolo valore di fattura.

3

Risposta alla domanda di modifica:

così che cosa devo fare per un semplice "? Tack questo fino alla fine (o all'inizio, qualsiasi altra cosa) di questa lista" Crea una funzione che prende un elenco e BillingInfo come argomenti, quindi restituisce un elenco?

Ah, ma c'è già una "funzione" per anteponendo un elemento per elencare: è il cons (:) costruttore :-)

Quindi il codice esistente funzionerà bene, fino a quando si don usare lo stesso nome per due variabili diverse, perché il binding del secondo nome ombreggia (nasconde) il primo.

ghci> let first_order_list = [Invoice 2345] 
ghci> first_order_list 
[Invoice 2345] 
ghci> let second_order_list = CashOnDelivery : first_order_list 
ghci> second_order_list 
[CashOnDelivery, Invoice 2345] 

Per quanto riguarda la seconda modifica:

Dal momento che il chiedere come si dovrebbe fare qualcosa di simile in un programma vero e proprio, direi questo:

Se le cose più volte aggiungendo ad una lista non vuoi inventare nuovi nomi per quella lista ogni volta. Ma la parola chiave qui è "ripetutamente", in una programmazione imperativa si usa un loop qui (e si modifica una variabile nel loop).

Poiché questa è una programmazione funzionale, non è possibile utilizzare i cicli, ma è possibile utilizzare la ricorsione. Ecco come scriverei un programma che consente a un utente di inserire ordini e raccogliere un elenco:

main = do 
    orderList <- collectBillingInfos 
    putStrLn ("You entered these billing infos:\n" ++ show orderList) 

collectBillingInfos :: IO [BillingInfo] 
collectBillingInfos = loop [] 
    where 
    loop xs = do 
     putStrLn "Enter billing info (or quit)" 
     line <- getLine 
     if line /= "quit" 
     then loop (parseBillingInfo line : xs) 
     else return xs 

parseBillingInfo :: String -> BillingInfo 
parseBillingInfo _ = CashOnDelivery -- Don't want to write a parser here 

Per ricapitolare; la funzione loop si chiama in modo ricorsivo, ogni volta con un nuovo elemento aggiunto all'elenco. Fino a quando l'utente non "chiude", quindi smette di chiamarsi e restituisce l'elenco finale.


risposta originale relativo alla valutazione pigra:

Come altri hanno detto, questa è una definizione ricorsiva, rendendo order_list una lista infinita contenente solo CashOnDelivery valori. Mentre la valutazione lazy non ne è la causa, lo rende utile.

A causa della valutazione pigra è possibile utilizzare order_list come questo:

ghci> take 3 order_list 
[CashOnDelivery, CashOnDelivery, CashOnDelivery] 

Se non aveste valutazione pigra, la chiamata a take potrebbe andare in crash, perché sarebbe in primo luogo cercare di valutare order_list (che è infinita).

Ora, per order_list questo non è veramente utile, ma ci sono molti altri luoghi in cui è molto conveniente poter programmare con strutture di dati infinite (o solo molto grandi).

1

penso che l'importante questione qui non è la pigrizia ma ambito. L'espressione let x = ... introduce una nuova definizione di x che sostituirà qualsiasi definizione precedente. Se x viene visualizzato sul lato destro, la definizione sarà ricorsiva. Sembra che vi aspettavate l'uso del order_list sul lato destro della

let order_list = CashOnDelivery : order_list 

fare riferimento alla prima definizione di order_list (vale a dire, [Invoice 2345]). Ma l'espressione let ha introdotto un nuovo ambito. Invece, hai definito un elenco infinito di elementi CashOnDelivery.

0

order_list in questa linea:

let order_list = [Invoice 2345] 

è una variabile diversa da order_list in questa linea

let order_list = CashOnDelivery : order_list 

La seconda riga non cambia il valore di order_list. Introduce una nuova variabile con lo stesso nome ma un valore diverso.

order_list sul lato destro della seconda riga è lo stesso order_list come sul lato sinistro della seconda riga; non è correlato allo order_list nella prima riga. Si ottiene un elenco infinito pieno di CashOnDelivery --- non ci sono Invoice s in questa seconda lista.

6

Come programmatore di recupero di ML, mi ritrovo da questo per tutto il tempo. È uno dei pochi fastidi di Haskell che non puoi facilmente riassociare un nome nelle clausole let o where. Se si desidera utilizzare let o where, è necessario inventare nuovi nomi. Nel ciclo di lettura e scrittura di livello superiore, se si desidera associare un nome alla volta, non si ha altra scelta. Ma se si è disposti a costrutti nido, si può abusare la notazione do con la monade identità:

import Control.Monad.Identity 

let order_list = runIdentity $ do 
     order_list <- return [Invoice 2345] 
     order_list <- return $ CashOnDelivery : order_list 
     return order_list 

Sì, questo codice è spregevole --- e non vale la pena per questo esempio --- ma se devo una lunga lista di rebindings potrei ricorrere ad essa quindi non devo inventare 5 o 6 varianti senza senso sullo stesso nome.

+0

Heh, vorrei finire con order_list, order_list ', order_list' ', order_list' '', ecc ... – ephemient

+0

Hehe, lo faccio a volte nella monade IO, per darmi una bella sensazione imperativa ;-) –

0

In ML, val non è ricorsivo. È necessario specificare val rec per i valori ricorsivi.

val fin = fn _ => 0 
val fin = fn x => fin x + 1 
(* the second `fin` is calling the first `fin` *) 

val rec inf = fn x => inf x + 1 
(* ML must be explicitly told to allow recursion... *) 
fun inf' x = inf' x + 1 
(* though `fun` is a shortcut to define possibly recursive functions *) 

In Haskell, tutte di alto livello, let e where attacchi sono ricorsivi - non c'è non ricorsiva vincolante.

let inf = \_ -> 0 
let inf = \x -> inf x + 1 
-- the second `inf` completely shadows the first `inf` 

L'eccezione: in do, <- vincolante non è ricorsiva. Tuttavia, se si utilizza {-# LANGUAGE RecursiveDo #-} e si importa Control.Monad.Fix, si ottiene mdo, in cui l'associazione <- è ricorsiva.

foo :: Maybe [Int] 
foo = do 
    x <- return [1] 
    x <- return (0 : x) -- rhs `x` refers to previous `x` 
    return x 
-- foo == Just [0, 1] 

bar :: Maybe [Int] 
bar = mdo 
    y <- return (0 : y) -- rhs `x` refers to lhs `x` 
    return y 
-- bar == Just [0, 0, 0, ...] 
0

magari provare questo

Facciamo una funzioneper fare questo. (Come in divertimento finzionale di programmazione)

$ let order_list = [Invoice 2345]
$ let f x = x : order_list
$ let order_List = f cashOnDelivery
$ order_list
[CashOnDelivery, [Invoice 2345]]

~ Nota dobbiamo riformulare la funzione let f x = x : order_list ogni volta che aggiungiamo l'order_list in modo che lo stiamo fissando all'ultimo order_list

Cheers!

ps, ​​l'incredibile potenza della programmazione funzionale è la possibilità di eseguire illimitate quantità di funzioni e oggetti su sistemi asincroni (paralleli/superveloci) senza interruzioni perché tutte le funzioni e gli oggetti sono indipendenti per definizione.