2012-02-22 15 views
8

ho scritto una funzione Haskell che divide un elenco xs in (init xs, last xs) in questo modo:Rileva valore di fondo in Haskell

split xs = split' [] xs 
    where 
     split' acc (x:[]) = (reverse acc, x) 
     split' acc (x:xs) = split' (x:acc) xs 

Da una lista vuota non può essere divisa in questo modo, non v'è alcuna corrispondenza per la lista vuota. Tuttavia, non volevo semplicemente la funzione error .... Così ho definito il seguente:

split [] = ([], undefined) 

Grazie alla valutazione pigra posso quindi definire una cassaforte init che restituisce semplicemente la lista vuota per la lista vuota:

init' = fst . split 

C'è qualche modo come ho potuto rilevare l'indefinito se ho cercato di accedervi, in modo tale che

last' xs 
    | isUndefined (snd xs) = ... 
    | otherwise   = ... 

so circa Maybe e Either, e che quelli sono una scelta migliore per esprimere ciò che voglio. Tuttavia mi sono chiesto se c'è un modo per rilevare un valore reale non definito, cioè in termini di errori di cattura, come la cattura di eccezioni.

+3

La risposta semplice è NO. Non è possibile rilevare undefined. (Una risposta più complicata è che puoi trovare eccezioni nel momad di I/O, ma non è quello che vuoi fare qui.) – augustss

risposta

10

A causa di fondo sussume non terminazione, la funzione isUndefined avrebbe dovuto risolvere il problema della terminazione e, quindi, non può esistere

Tuttavia, anche se esistesse, non si poteva ancora sapere se il valore indefinito nel 2 ° elemento della tupla era stato inserito nella funzione split o se l'ultimo elemento della lista era già indefinito.

+1

Se esistesse una funzione che risolvesse il problema di interruzione, potremmo mostrare False, e quindi, potremmo mostrare tutto, incluso "possiamo dire se il valore indefinito nel 2 ° elemento della tua tupla è stato messo lì attraverso la tua funzione di divisione o se il l'ultimo elemento della lista era già indefinito. " –

8

La funzione error non fa nulla fino a quando non viene valutata, in modo da poter fare qualcosa di simile:

split [] = ([], error "split: empty list") 

last' = snd . split 
+0

La risposta migliore qui, secondo me, perché risponde alla domanda successiva che avrebbe avuto in ogni caso scravy: " Cosa inserisco nella clausola 'altrimenti'?". –

+0

Lo so, come ho detto "grazie alla valutazione lazy" - mettere l'errore è come mettere indefinito lì. Volevo scoprire proprio questa cosa, ma come ho imparato non è assolutamente possibile dato che la semantica del basso lo proibisce. – scravy

+0

+1 per usare 'error' con un messaggio informativo. La scelta dell'OP di "Non volevo' error ... '" e l'uso di 'undefined' porta in genere alla frustrazione quando si esegue il debug:" Sì, ho colpito 'undefined', ma * quale uso * di' undefined'? ". Questo è particolarmente grave in Haskell (GHC), poiché la pigrizia e l'ottimizzazione aggressiva possono far eseguire un programma compilato in un ordine inaspettato. Le tracce dello stack sono state recentemente aggiunte a GHC, ma sono ancora opt-in e una specie di illusione piuttosto che una vera registrazione di come il binario viene effettivamente eseguito. Molto più facile dirci semplicemente che cosa è andato storto in primo luogo! – Warbo

11

undefined non è migliore di utilizzare error. Infatti, in undefined Prelude è defined come

undefined = error "Prelude.undefined" 


Ora, una funzione che non può risultare in un error è chiamato "funzione totale", cioè esso è valido per tutti i valori di ingresso.

La funzione split che avete attualmente implementato ha la firma

split :: [a] -> ([a], a) 

Questo è un problema, dal momento che la firma di tipo promette che il risultato contiene sempre una lista e un elemento, che è chiaramente impossibile da prevedere liste vuote di tipo generico.

Il modo canonico in Haskell per risolvere questo è modificare la firma del tipo per indicare che a volte non abbiamo un valore valido per il secondo elemento.

split :: [a] -> ([a], Maybe a) 

Ora è possibile scrivere una corretta applicazione per il caso in cui si ottiene una lista vuota

split [] = ([], Nothing) 
split xs = split' [] xs 
    where 
     split' acc (x:[]) = (reverse acc, Just x) 
     split' acc (x:xs) = split' (x:acc) xs 

Ora è in grado di rilevare il caso valore mancante dal modello-matching

let (init', last') = split xs 
in case last' of 
    Nothing -> ... -- do something if we don't have a value 
    Just x -> ... -- do something with value x 
+3

Un po 'ironico definito indefinito;) –

+1

@DanBurton Beh, se questo è il tuo tipo di umorismo, nota che è anche possibile definire "non definito" come questo: "undefined = undefined". Si comporta diversamente (questa definizione non termina quando forzata), ma semanticamente ha lo stesso valore (in basso). –

4

Da the Haskell 2010 Language Report > Introduction # Values and Types

Gli errori in Haskell sono semanticamente equivalenti a ⊥ ("in basso"). Tecnicamente, sono indistinguibili da non terminazione, quindi il linguaggio non include alcun meccanismo per rilevare o agire sugli errori.

Per chiarezza, undefined è destinato ad essere un modo per inserire ⊥ nel programma, e dato che (come osservato shang) undefined è definito in termini di error, v'è, pertanto, "alcun meccanismo per rilevare o agendo su undefined ".

+1

Anche se questo è corretto dal punto di vista di Haskell 2010, vale la pena notare che in GHC Haskell alcuni valori,, incluso 'error' sono implementati come eccezioni, che possono essere catturati nella monade' IO'. Tuttavia, fare questo è di solito indicativo di una cattiva progettazione, in quanto vi sono modi migliori per gestire gli errori nel codice puro. (Esiste anche un'eccezione 'NonTermination' che può essere generata quando il runtime può rilevare la non-terminazione, sebbene questo non sia certo affidabile a causa del problema di interruzione). – hammar

1

Sebbene la risposta di Ingo semantica sia corretta, se si utilizza GHC, è possibile utilizzare un paio di funzioni "non sicure" che, sebbene non proprio perfette come se si passasse a un calcolo di tipo IO a che contiene un'eccezione restituirà True, funziona. È un po 'un trucco però :).

import Control.Exception 
import System.IO.Unsafe 
import Unsafe.Coerce 

isUndefined :: a -> Bool 
isUndefined x = unsafePerformIO $ catch ((unsafeCoerce x :: IO()) >> return False) (const $ return True :: SomeException -> IO Bool) 

So che questo è orribile, ma nondimeno funziona. Non rileverà però nessuna terminazione;)

+0

Questo può essere migliorato sostituendo "return False" con qualcosa del tipo "(\ e -> return $ show e ==" Prelude.undefined ")" con praticamente il 100% di accuratezza nella pratica (a meno che qualcuno non crei un'eccezione che implementa di show return "Prelude.undefined", ma sarebbe sciocco ...) –

Problemi correlati