2015-05-05 7 views
7

Mehr als oft nicht, dass ich Funktionen schreibe, dass der einzige Konstruktor eines neuen Typs sind Strippen, wie in der folgenden Funktion das erste Argument zurück, das nicht Nichts ist:Strippen des newtype Konstruktor

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

I denke, das Lambda ist unnötig ausführlich. Ich möchte so etwas schreiben:

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

die Meta-Programmiermöglichkeiten von Haskell Sie für alles erlauben ähnlich wie das? Jede andere Lösung, um dieses Problem in kürzerer Zeit zu lösen, ist ebenfalls willkommen.

UPD. Der gesamte Code wird angefordert:

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

Klingt wie 'erzwingen'. – Zeta

+0

Wie soll der Prozess aussehen? Es könnte möglich sein, das 'newtype'-Paket zu verwenden, um das meiste davon zu verbergen. Alles was ich daraus machen kann, ist, dass 'Pick' zu einem rekursiven Typ gehören muss, da' Pick :: [a] -> PickType' und 'process :: PickType -> Maybe a', aber' First. process :: PickType -> Erste a', also 'xs :: [PickType]'? – bheklilr

+0

Es ist nur ein Spielzeugbeispiel, aber ich werde es dem OP hinzufügen. – NioBium

Antwort

3

Als Zeta vorgeschlagen in den Kommentaren, ist coerce ein schöner, allgemein um dies zu tun:

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

Andere Schöne an coerce ist, dass Sie es verwenden können, „innerhalb“ eines Typkonstruktor ohne Laufzeitkosten zu zwingen, wie folgt aus:

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

Die Alternative, map getFirst, würde eine Laufzeitaufwand für die map Traversal induzieren.

Auch jedes Mal, wenn ein newtype machen, GHC macht automatisch die entsprechende Coercible Instanz, so dass Sie nie über Messing mit den zugrunde liegenden Maschinen zu kümmern (Sie brauchen nicht einmal deriving für sie):

newtype Test = Test Char 

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

Wenn Sie Data.Monoid.First verwenden, dann ist dies nur getFirst. Viele Wrapper des Typs newtype verwenden die Record-Syntax, um eine einfache Funktion zum Entpacken des newtype zu bieten.

4

Meta-Programmierung sieht dafür zu komplex aus. Ich würde einfach

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

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

verwenden Es ist oft der Fall, dass eine Funktion zusammen mit dem newtype definiert ist, z.B.

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

Danke. Das ist besser, aber wenn ich viele neue Typen schreiben muss, wäre es immer noch schön, etwas zu haben, wie ich es gewünscht habe. Auch wenn es scheint, dass Aufzeichnungen das Beste sind, was ich tun kann, habe ich ein persönliches Vorurteil gegen sie (in dieser Form erscheinen sie auch redundant und wiederholend; selbst du hast einen Fehler gemacht, dem Namen des Konstrukteurs und "un" vorangestellt zu sein, im Gegensatz zu "bekommen", und meiner Meinung nach sollte es eine einheitliche Behandlung dieses Falles geben). – NioBium

+0

@NioBium Der einzige aktuelle einheitliche Weg ist, wie Sie schreiben, '(\ (erstes x) -> x)' das ist umständlich, aber nicht so lang. Ich stimme jedoch zu, dass ein direkterer einheitlicher Weg schön sein könnte. In den Bibliotheken finden Sie auch 'run-' als gemeinsames Präfix für die inverse Operation, zumindest im Kontext von monadischen newtypes. – chi

5

In diesem Fall können Sie tatsächlich die newtypes Paket, um dieses Problem allgemeiner zu lösen:

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 

Sie auch eine generische Version haben könnte, die eine Newtype n (Maybe String) wie

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 
nimmt

Dann

> 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" 

Als Erklärung dafür, wie das funktioniert, nimmt die ala' Funktion eine newtype Wrapper die Instanz von Newtype zu verwenden, eine Funktion, die wir in diesem Fall foldMap sein wollen, um zu bestimmen:

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

seit foldMap f endet Da eine verallgemeinerte mconcat . map f über Foldable Typen statt nur Listen ist, dann eine Funktion die als "Präprozessor" zum Einhängen in die übergeordnete Funktion verwendet werden soll, übergeben an ala' (foldMap), dann in diesem Fall etwas Foldable t => t Node zu verarbeiten. Wenn Sie den Vorverarbeitungsschritt nicht möchten, verwenden Sie einfach ala, der id für seinen Präprozessor verwendet. Die Verwendung dieser Funktion kann aufgrund ihres komplexen Typs manchmal schwierig sein, aber wie die Beispiele in der Dokumentation zeigen, ist foldMap oft eine gute Wahl.

Die Macht ist, wenn Sie für Maybe String Ihre eigenen newtype Wrapper schreiben wollen:

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 

Dann

> process' firstAsCaps input 
Just "BAR" 
Verwandte Themen