2011-10-01 12 views
7

ho scritto il seguente codice per rimuovere le vocali da una frase:Tipo di dati vocale in Haskell, è possibile?

main = print $ unixname "The House" 

    vowel x = elem x "aeiouAEIOU" 

    unixname :: [Char] -> [Char] 
    unixname [] = [] 
    unixname (x:xs) | vowel x = unixname xs 
      | otherwise = x : unixname xs 

solo chiedendo se è possibile creare un tipo di dati per vocale? Il compilatore non mi permetterà di usare caratteri in un tipo di dati.

+2

non ero consapevole del fatto che le vocali non sono stati ammessi nei nomi UNIX;) –

+0

Perché vuoi un "tipo di dati per vocale"? Cosa farebbe? –

+2

@Anschel, presumibilmente un 'Char' i cui valori possono solo assumere vocali. Sarebbe un modo per dichiarare uno dei criteri di correttezza per 'unixname'. Ehm ... in realtà non perché ha bisogno del suo complemento, ma tu hai l'idea. – luqui

risposta

15

Non direttamente. Il problema è che i caratteri sono un tipo built-in senza alcuna possibilità di polimorfismo. Questo è diverso dai letterali numerici, che sono progettati per essere polimorfici tramite la classe di tipo Num.

Detto questo, ci sono due approcci di base che è possibile adottare: un wrapper newtype con un costruttore intelligente o un tipo completamente nuovo.

L'involucro Newtype è più facile da usare:

module Vowel (Vowel, vowel, fromVowel) where 

newtype Vowel = Vowel Char 

vowel :: Char -> Maybe (Vowel) 
vowel x | x `elem` "aeiouAEIOU" = Just (Vowel x) 
     | otherwise = Nothing 

fromVowel :: Vowel -> Char 
fromVowel (Vowel x) = x 

Poiché il costruttore Vowel non viene esportata, nuove Vowel s possono essere creati solo dalla funzione vowel, che ammette solo i caratteri desiderati.

Si potrebbe anche fare un nuovo tipo come questo:

data Vowel = A | E | I | O | U | Aa | Ee | Ii | Oo | Uu 

fromChar :: Char -> Maybe Vowel 
fromChar 'a' = Just Aa 
fromChar 'A' = Just A 
-- etc. 

toChar :: Vowel -> Char 
toChar Aa = 'a' 
toChar A = 'A' 

Questo secondo modo è abbastanza pesante, e quindi è molto più scomodi da usare.

Ecco come si fa. Non sono abbastanza certo che tu voglia farlo. Il solito idioma è quello di creare tipi che rappresentano i tuoi dati, e in particolare lo non rappresenta rappresentare le vocali. Un modello comune sarebbe qualcosa di simile:

newtype CleanString = Cleaned { raw :: String } 

-- user input needs to be sanitized 
cleanString :: String -> CleanString 

Qui il newtype distingue tra ingresso unsanitized e igienizzati. Se l'unico modo per fare un CleanString è di cleanString, allora sai staticamente che ogni CleanString è correttamente disinfettato (a condizione che cleanString sia corretto). Nel tuo caso, sembra che tu abbia effettivamente bisogno di un tipo per le consonanti, non per le vocali.

I nuovi tipi in Haskell sono molto leggeri *, ma il programmatore deve scrivere e utilizzare il codice per eseguire il wrapping e lo unwrapping. In molti casi i benefici superano il lavoro extra. Tuttavia, non riesco a pensare a nessuna applicazione in cui è importante sapere che il tuo String è privo di vocale, quindi probabilmente lavorerei solo con un semplice String.

* I newtypes esistono solo in fase di compilazione, quindi in teoria non ci sono costi di runtime per l'utilizzo. Tuttavia, la loro esistenza può cambiare il codice prodotto (ad esempio inibendo le RULE), quindi a volte c'è un impatto misurabile sulle prestazioni.

8

È possibile utilizzare phantom types per contrassegnare i caratteri con informazioni aggiuntive, al fine di garantire al sistema i tipi di garanzia durante la compilazione che le stringhe contengono solo, ad esempio, le vocali o le non-vocali.

Ecco un esempio giocattolo:

{-# LANGUAGE EmptyDataDecls #-} 

import Data.Maybe 

newtype TaggedChar a = TaggedChar { fromTaggedChar :: Char } 

data Vowel 
data NonVowel 

isVowel x = x `elem` "aeiouyAEIOUY" 

toVowel :: Char -> Maybe (TaggedChar Vowel) 
toVowel x 
    | isVowel x = Just $ TaggedChar x 
    | otherwise = Nothing 

toNonVowel :: Char -> Maybe (TaggedChar NonVowel) 
toNonVowel x 
    | isVowel x = Nothing 
    | otherwise = Just $ TaggedChar x 

unixname :: [Char] -> [TaggedChar NonVowel] 
unixname = mapMaybe toNonVowel

Il vantaggio di questo approccio è che si può ancora anche scrivere funzioni che lavorano su tutti i TaggedChars indipendentemente dal tag.Per esempio:

toString :: [TaggedChar a] -> String 
toString = map fromTaggedChar
Problemi correlati