2014-10-06 20 views
7

Sagen wir, ich habe folgendes newtype:Verwendung von Funktionen von `a` auf` newtype a`

newtype Foo = Foo Integer deriving (Eq, Show)

Gibt es eine prägnante Art und Weise zwei Foo ‚s hinzuzufügen:

(Foo 10) + (Foo 5) == Foo 15

oder erhalten Sie die max:

max (Foo 10) (Foo 5) == Foo 5?

Ich bin neugierig, ob es möglich ist, leicht Funktionen nutzen von a für eine newtype a anstatt tun:

addFoo :: Foo -> Foo -> Foo 
addFoo (Foo x) (Foo y) = Foo $ x + y 

Antwort

12

Wie haskell98 weiß, wie man jene Eq und Show Instanzen ableiten, können Sie schalten Sie die GeneralizedNewtypeDeriving Erweiterung GHC die Num und Ord Instanzen, die Sie brauchen:

Prelude> :set -XGeneralizedNewtypeDeriving 
Prelude> newtype Foo = Foo Integer deriving (Eq, Show, Num, Ord) 
Prelude> (Foo 10) + (Foo 5) == Foo 15 
True 
Prelude> max (Foo 10) (Foo 5) == Foo 5 
False 
+0

Wird diese Technik als idiomatisch angesehen? –

+3

@KevinMeredith Ja, es ist idiomatisch. Aber nur zu deiner eigenen Erleuchtung solltest du versuchen, die "Ord" und "Num" Instanzen selbst zu schreiben. – augustss

2

mathematische Operationen zu erhalten, müssen Sie Foo eine Instanz der Num typeclass machen. Dies bedeutet, dass Sie (+), (*), abs, signum, fromInteger und entweder negate oder (-) für Foo definieren müssen. Sobald Sie diese Funktionen definiert haben, erhalten Sie die restlichen Funktionen, die an Num arbeiten, kostenlos.

Um max und ähnliche Funktionen zu erhalten, müssen Sie Foo eine Instanz von Ord machen. Dies erfordert Definitionen von compare oder (<=).

Im Allgemeinen können Sie :t in ghci verwenden, um den Typ einer Funktion zu finden, die die Typklassen enthält, mit denen sie arbeitet. Dann müssen Sie nur bestimmen, welche minimale Menge von Funktionen Sie für diese Typklasse definieren müssen.

10

Sie möchten Funktionen heben vom Typ Integer -> Integer -> Integer bis Foo -> Foo -> Foo. Um dies zu tun könnten Sie Utility-Funktionen definieren:

liftFoo :: (Integer -> Integer) -> Foo -> Foo 
liftFoo f (Foo a) = Foo $ f a 

liftFoo2 :: (Integer -> Integer -> Integer) -> Foo -> Foo -> Foo 
liftFoo2 f (Foo a) (Foo b) = Foo $ f a b 

-- and so on 

Dann könnten Sie es wie folgt verwenden:

liftFoo2 (+) (Foo 10) (Foo 5) 

liftFoo2 max (Foo 10) (Foo 5) 

Dies hat den Vorteil, keine Verlängerung erforderlich ist.


Eine weitere Option ist die Definition des Foo newtype mehr zulässig zu machen, so dass Sie es eine Instanz von Functor und Applicative machen könnte:

:

import Control.Applicative 

newtype Foo a = Foo a deriving (Eq, Show) 

foo :: Integer -> Foo Integer 
foo = Foo 

instance Functor Foo where 
    fmap f (Foo a) = Foo $ f a 

instance Applicative Foo where 
    pure = Foo 
    (Foo f) <*> (Foo a) = Foo $ f a 

Nun könnte man folgendes tun

(+) <$> foo 10 <*> foo 5 

max <$> foo 10 <*> foo 5 

Da foo auf den Integer Typ spezialisiert ist, verlieren Sie keine Vorteile der Typprüfung.

3

Sie können dazu auch Safe Coercions verwenden. Grob verwenden Sie Data.Coerce.coerce, um den newtype automatisch zu verpacken/auszupacken.

> import Data.Coerce 
> newtype Foo = Foo Integer deriving (Eq, Show, Ord) 
> coerce (Foo 1) :: Integer 
1 
> let f :: Integer -> Integer ; f x = x + 1 
> coerce f (Foo 10) 
Foo 11 
> coerce (succ :: Integer -> Integer) (Foo 10) :: Foo 
Foo 11 
> coerce (max :: Integer -> Integer -> Integer) (Foo 10) (Foo 5) :: Foo 
Foo 10 

Beachten Sie, dass es mit monomorphic Funktionen wie f, aber weniger mit polymorphen Funktionen wie succ, da in diesem Fall erforderlich, um eine Typanmerkung ist große Werke.