2015-05-05 20 views
7

Più spesso di quanto non sto funzioni che sono stripping l'unico costruttore di un nuovo tipo, come ad esempio nella seguente funzione per restituire il primo argomento che non è niente di scrittura:Spogliarello il costruttore newtype

process (Pick xs) = (\(First x) -> x) . mconcat . map (First . process) $ xs 

I pensa che il lambda sia inutilmente prolisso. Vorrei scrivere qualcosa del genere:

process (Pick xs) = -First . mconcat . map (First . process) $ xs 

fare gli impianti di programmazione meta di Haskell consentono di qualcosa di simile a questo? Qualsiasi altra soluzione per risolvere questo problema in modo più conciso è anche benvenuta.

UPD. è stato richiesto l'intero codice:

data Node where 
    Join :: [Node] -> Node 
    Pick :: [Node] -> Node 
    Given :: Maybe String -> Node 
    Name :: String -> Node 

process :: Node -> Maybe String 
process (Join xs) = liftM os_path_join (mapM process xs) 
process (Pick xs) = getFirst . mconcat . map (First . process) $ xs 
process (Name x) = Just x 
process (Given x) = x 
+0

Sembra "coercizione". – Zeta

+0

Qual è il tipo di 'processo' che dovrebbe essere? Potrebbe essere possibile usare il pacchetto 'newtype' per nascondere la maggior parte di questo. Tutto ciò che posso fare è che 'Pick' deve appartenere a un tipo ricorsivo, poiché' Pick :: [a] -> PickType', e 'process :: PickType -> Forse a', ma' Primo. process :: PickType -> First a', quindi 'xs :: [PickType]'? – bheklilr

+0

È solo un esempio di giocattolo, ma lo aggiungerò all'OP. – NioBium

risposta

3

Come Zeta suggerito nei commenti, coerce è un bel modo, generale per fare ciò:

process (Pick xs) = coerce . mconcat . map (First . process) $ xs 

Un altro cosa bella di coerce è che si può utilizzare per costringere "dentro" di un costruttore di tipo a costo zero fase di esecuzione, in questo modo:

example :: [Sum Int] -> [Int] 
example = coerce 

L'alternativa, map getFirst, indurrebbe un overhead di runtime per il map attraversamento.

Inoltre, ogni volta che si effettua una newtype, GHC rende automaticamente l'appropriata Coercible istanza in modo da non dovete preoccuparvi di fare scherzi con il macchinario sottostante (che non hanno nemmeno bisogno deriving per esso):

newtype Test = Test Char 

example2 :: Maybe Test -> Maybe Char 
example2 = coerce 
4

Se stai usando Data.Monoid.First, allora questo è solo getFirst. Molti wrapper newtype usano la sintassi del record per fornire una funzione semplice per scartare il newtype.

4

La programmazione meta sembra eccessivamente complessa per questo. Vorrei semplicemente usare

unFirst (First x) = x -- define once, use many times 

process (Pick xs) = unFirst . mconcat . map (First . process) $ xs 

Spesso accade che una funzione sia definita insieme al newtype, ad es.

newtype First a = First { unFirst :: a } 
+0

Grazie. Questo è meglio, ma se ho bisogno di scrivere un sacco di newtypes, sarebbe comunque bello avere qualcosa, come quello che ho richiesto. Anche se a questo punto sembra che le registrazioni siano le migliori che potrei fare, ho un pregiudizio personale contro di loro (in questa forma sembrano ridondanti e ripetitivi, anche se hai fatto un errore nel prefisso del nome del Costruttore con e "un", al contrario di "get", e secondo me dovrebbe esistere un trattamento uniforme di questo caso). – NioBium

+0

@NioBium L'unico modo uniforme corrente è, come si scrive, '(\ (First x) -> x)' che è ingombrante ma non così lungo. Sono comunque d'accordo sul fatto che avere un modo più diretto e uniforme potrebbe essere bello. Nelle librerie trovi anche 'run-' come prefisso comune per l'operazione inversa, almeno nel contesto dei newtypes monadici. – chi

5

In questo caso si può effettivamente utilizzare il pacchetto newtypes per risolvere questo problema più genericamente:

process :: Node -> Maybe String 
process (Pick xs) = ala' First foldMap process xs 
process (Join xs) = liftM os_path_join (mapM process xs) 
process (Name x) = Just x 
process (Given x) = x 

si potrebbe anche avere una versione più generica che prende un Newtype n (Maybe String) come

process' 
    :: (Newtype n (Maybe String), Monoid n) 
    => (Maybe String -> n) -> Node -> Maybe String 
process' wrapper (Pick xs) = ala' wrapper foldMap (process' wrapper) xs 
process' wrapper (Join xs) = liftM os_path_join (mapM (process' wrapper) xs) 
process' wrapper (Name x) = Just x 
process' wrapper (Given x) = x 

Quindi

> let processFirst = process' First 
> let processLast = process' Last 
> let input = Pick [Given Nothing, Name "bar", Given (Just "foo"), Given Nothing] 
> processFirst input 
Just "bar" 
> ProcessLast input 
Just "foo" 

come spiegazione per come funziona, la funzione ala' prende un involucro Newtype per determinare l'istanza di Newtype da usare, una funzione che in questo caso vogliamo essere foldMap:

foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m 

dal foldMap f finisce essendo un tipo generalizzato mconcat . map f su Foldable anziché solo liste, quindi una funzione da utilizzare come "preprocessore" per l'aggancio alla funzione di ordine superiore passata a ala' (foldMap), quindi in questo caso alcuni Foldable t => t Node da elaborare. Se non si desidera eseguire la fase di pre-elaborazione, utilizzare semplicemente ala, che utilizza id per il relativo preprocessore. L'uso di questa funzione a volte può essere difficile a causa del suo tipo complesso, ma come gli esempi nella documentazione mostrano foldMap è spesso una buona scelta.

La potenza di questo è che se si voleva scrivere il proprio newtype wrapper per Maybe String:

newtype FirstAsCaps = FirstAsCaps { getFirstAsCaps :: Maybe String } 

firstAsCaps :: Maybe String -> FirstAsCaps 
firstAsCaps = FirstAsCaps . fmap (fmap toUpper) 

instance Monoid FirstAsCaps where 
    mempty = firstAsCaps Nothing 
    mappend (FirstAsCaps f) (FirstAsCaps g) 
     = FirstAsCaps $ ala First (uncurry . on (<>)) (f, g) 

instance Newtype FirstAsCaps (Maybe String) where 
    pack = firstAsCaps 
    unpack = getFirstAsCaps 

Poi

> process' firstAsCaps input 
Just "BAR" 
Problemi correlati