2015-08-22 4 views
10

HoPosso abbinare un carattere jolly del costruttore di dati in Haskell?

data Foo = X (String, Int) | A String | B String | C String | D String -- ... 

e ho definito

f (X (s, _)) =s 
f (A s) = s 
f (B s) = s 
f (C s) = s 
f (D s) = s 
-- ... 

ma preferirei essere in grado di scrivere qualcosa di simile

f (X (s, _)) =s 
f (_ s) = s 

Ma sembra che ci sia alcun modo per farlo (I ottenere un "errore di analisi" associato allo _).

Esiste un modo per associare un carattere jolly del costruttore di dati in Haskell?

+0

Anche se non è disponibile nella lingua questo tipo di funzionalità (eccetto, si potrebbe considerare l'utilizzo di [generics] (https://wiki.haskell.org/Generics) o [Template Haskell] (https: //wiki.haskell. org/Template_Haskell) per generare tale codice Sarebbe ragionevole per il tuo caso d'uso? –

risposta

15

No. Ma puoi scrivere questo:

data Foo 
    = X { name :: String, age :: Int } 
    | A { name :: String } 
    | B { name :: String } 
    | C { name :: String } 
    | D { name :: String } 

e quindi avere name :: Foo -> String. Si potrebbe anche prendere in considerazione questo:

data Tag = A | B | C | D 
data Foo = X String Int | Tagged Tag String 

f (X s _) = s 
f (Tagged _ s) = s 
+0

Suppongo che la scelta degli approcci (quella che stavo usando rispetto alla tua seconda, almeno) dipende da quanti "taggati" uno ci sono In pratica ne ho solo alcuni (e ci sono altri costruttori con forme leggermente diverse) e penso che l'approccio che ho potrebbe essere più pulito C'è un motivo * concettuale * per scegliere uno di questi tre approcci rispetto ad un altro? – orome

+0

@ raxacoricofallapatorius Qual è il senso che dipende molto da ciò che vuoi che il tipo significhi - proprio come potresti scrivere '2 * 1 + 2 * 2 + 2 * 3' in alcuni casi e' 2 * (1 + 2 + 3) ' in altri per enfatizzare motivi diversi per essere interessato al numero '12'. –

+1

@raxacoricofallapatorius, se le stringhe rappresentano la stessa cosa in ogni caso, l'approccio del tag ha senso. Se le stringhe non sono correlate, probabilmente vuoi tre costruttori. Il fatto che tu voglia fare la stessa cosa per le stringhe induce a pensare, ma non implica, che l'approccio taggato ha senso. – dfeuer

3

Oltre a @ di DanielWagner risposta, in alternativa è quella di utilizzare Scrap your boilerplate (AKA "SYB"). Ti permette di trovare il primo sottotipo di un determinato tipo. Così si potrebbe definire

{-# LANGUAGE DeriveDataTypeable #-} 

import Control.Monad 
import Data.Data 
import Data.Generics.Schemes 
import Data.Typeable 

data Foo = X (String, Int) | A String | B String | C String | D String 
    deriving (Show, Eq, Ord, Data, Typeable) 

fooString :: Foo -> Maybe String 
fooString = msum . gmapQ cast 

e fooString tornerà il primo String argomento dei tuoi costruttori. Funzione cast filtri fuori String se gmapQ ottiene i valori filtrati per tutti i sottotemi immediati.

Tuttavia, questo non restituirà il String da X, perché X non ha immediata String sottotermine, si ha un solo sottotermine di tipo (String, Int). Al fine di ottenere il primo String ovunque nella gerarchia termine, è possibile utilizzare everywhere:

fooString' :: Foo -> Maybe String 
fooString' = everything mplus cast 

Si noti che questo approccio è un po 'fragile: Comprende semplicemente tutto String s che trova, che potrebbe non essere sempre quello che si vuole , in particolare, se in seguito si estende il tipo di dati (o alcuni tipi di dati a cui fa riferimento).