Newtype ist eine spezielle Datentypdeklaration. So dass es nur einen Konstruktor und ein Feld enthÀlt.
newtype Foo a = Bar a newtype Id = MkId Word

Typische Fragen fĂŒr Neulinge
Was ist der Unterschied zu Datentypdaten? data Foo a = Bar a data Id = MkId Word
Die HauptspezifitÀt des
Newtype besteht darin, dass er aus denselben Teilen besteht wie sein einziges Feld. Genauer gesagt unterscheidet es sich vom Original auf Typebene, hat jedoch die gleiche Darstellung im Speicher und wird streng (nicht trÀge) berechnet.
Kurz gesagt -
Newtype sind aufgrund ihrer PrÀsentation effektiver.
Ja, es bedeutet mir nichts ... Ich werde Daten verwendenNein, am Ende können Sie immer die Erweiterung
-funpack-strict-fields :) fĂŒr strenge (nicht faule) Felder
aktivieren oder direkt angeben
data Id = MkId !Word
Die Leistung des
Newtype ist jedoch nicht auf die Recheneffizienz beschrÀnkt. Sie sind viel stÀrker!
3 neue Rollen

Implementierung ausblenden
module Data.Id (Id()) where newtype Id = MkId Word
newtype unterscheidet sich vom ursprĂŒnglichen, intern nur
Word .
Wir verstecken den
MkId- Konstruktor jedoch auĂerhalb des Moduls.
Implementierung Implementierung
{-# LANGUAGE GeneralizedNewtypeDeriving #-} newtype Id = MkId Word deriving (Num, Eq)
Obwohl dies nicht im Haskell2010-Standard enthalten ist, können Sie durch Erweitern der Ausgabe generischer newTypes automatisch auf das Verhalten von
newtype schlieĂen, das dem Verhalten des internen Felds entspricht. In unserem Fall ist das Verhalten von
Gl. Id und
Num Id dasselbe wie bei
Gl. Wort und
Num Wort .
Durch den Ausbau der raffinierten Zucht (
DerivingVia ) kann viel mehr erreicht werden, dazu spÀter mehr.
Umsetzung der Wahl
In einigen FÀllen können Sie trotz eines eigenen Konstruktors Ihre interne Darstellung verwenden.
Herausforderung
Es gibt eine Liste von ganzen Zahlen. Finden Sie den Maximal- und Gesamtbetrag in nur einem Durchgang auf der Liste.
Und verwenden Sie keine
Foldl- und
Fold- Pakete .
Typische Antwort
NatĂŒrlich
falten ! :) :)
foldr :: Foldable t => (a -> b -> b) -> b -> ta -> b
Und die endgĂŒltige Funktion wird folgendermaĂen beschrieben:
aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = foldr (\el (m, s) -> (Just el `max` m, el + s)) (Nothing, 0)
Wenn Sie genau hinschauen, sehen Sie Àhnliche Operationen auf beiden Seiten:
Nur el `max` m und
el + s . In beiden FÀllen Mapping und BinÀroperation. Und die leeren Elemente sind
Nichts und
0 .
Ja, das sind Monoide!
Monoid und Halbgruppe im DetailEine Halbgruppe ist eine Eigenschaft einer assoziativen binÀren Operation
x â (y â z) == (x â y) â z
Ein Monoid ist eine Eigenschaft einer assoziativen Operation (d. H. Einer Halbgruppe).
x â (y â z) == (x â y) â z
Das hat ein leeres Element, das weder rechts noch links ein Element Àndert
x â empty == x == empty â x
Sowohl
max als auch
(+) sind assoziativ, beide haben leere Elemente -
Nothing und
0 .
Und die Kombination der Kartierung von Monoiden zusammen mit der Faltung ist
faltbar !
Faltbarer detaillierterErinnern Sie sich an die Definition des Faltens:
class Foldable t where foldMap :: (Monoid m) => (a -> m) -> ta -> m ...
Wenden wir das Faltverhalten auf
max und
(+) an . Wir können nicht mehr als eine Implementierung des
Word- Monoids organisieren. Es ist Zeit, die Implementierung der
Newtype- Auswahl zu nutzen!
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
Es ist notwendig, eine Bemerkung zu machen.
Tatsache ist, dass wir, um ein Monoid fĂŒr den Datentyp
Max a zu sein , ein minimales Element benötigen, dh, dass ein leeres Element existiert. So kann nur ein begrenzter
Max a ein Monoid sein.
Theoretisch korrektes maximales Elementmonoid newtype Max a = Max a instance Ord a => Semigroup (Max a) instance Bounded a => Monoid (Max a)
Irgendwie mĂŒssen wir also unseren Datentyp konvertieren, damit ein leeres Element erscheint und wir die Koagulation verwenden können.
Das konjugierte
Vielleicht- Element verwandelt eine Halbgruppe in ein Monoid!
Liberalisierung von BeschrĂ€nkungen in neueren Versionen von GHCZurĂŒck in GHC 8.2 war eine Monoid-Typ-EinschrĂ€nkung erforderlich
instance Monoid a => Monoid (Maybe a)
was bedeutet, wir brauchten einen anderen neuen Typ:
Und es ist bereits in GHC 8.4 viel einfacher, wo nur eine Halbgruppe mit TypbeschrÀnkung erforderlich ist und selbst kein Optionstyp erstellt werden muss.
instance Semigroup a => Monoid (Maybe a)
Faltreaktion
Nun aktualisieren Sie den Code mithilfe von Reduzierbarkeit und Pfeilen.
Wir erinnern uns, dass (.) Nur eine funktionale Zusammensetzung ist:
(.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (gx)
Und denken
Sie daran, dass
fmap ein Funktor ist:
fmap :: Functor f => (a -> b) -> fa -> fb
und seine Implementierung fĂŒr
Vielleicht wird etwas höher beschrieben.
Pfeil detaillierterPfeile sind Eigenschaften einiger Funktionen, mit denen Sie in einem Blockdiagramm damit arbeiten können.
Weitere Details finden Sie hier:
Pfeile: Eine allgemeine Schnittstelle zur BerechnungIn unserem Fall verwenden wir die Pfeilfunktion
Also
instance Arrow (->)
Wir werden die Funktionen verwenden:
(***) :: Arrow a => abc -> ab' c' -> a (b, b') (c, c') (&&&) :: Arrow a => abc -> abc' -> ab (c, c')
FĂŒr unseren Fall
abc == (->) bc == b -> c
Dementsprechend reduziert sich die Signatur unserer Funktionen auf:
(***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c')) (&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
Oder in ganz einfachen Worten, die Funktion
(***) kombiniert zwei Funktionen mit einem Argument (und einem Ausgabetyp) zu einer Funktion mit der Arbeit eines Paares von Argumenten am Eingang bzw. am Ausgang, einem Paar von Ausgabetypen.
Die Funktion
(&&&) ist eine abgespeckte Version
(***) , bei der der Typ der Eingabeargumente der beiden Funktionen gleich ist und bei der Eingabe nicht zwei Argumente, sondern ein Argument vorhanden sind.
Insgesamt hat die Vereinigungsfunktion die Form erhalten:
import Data.Semigroup import Data.Monoid import Control.Arrow aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = (fmap getMax *** getSum) . (foldMap (Just . Max &&& Sum))
Es stellte sich sehr kurz heraus!
Es ist jedoch immer noch anstrengend, Daten von verschachtelten Typen zu verpacken und zu invertieren!
Sie können es weiter reduzieren, und eine ressourcenfreie erzwungene Konvertierung hilft uns!
Sichere, ressourcenfreie erzwungene Konvertierung und Typrollen
Es gibt eine Funktion aus dem
Unsafe.Coerce- Paket -
unsafeCoerce import Unsafe.Coerce(unsafeCoerce) unsafeCoerce :: a -> b
Die Funktion zwangsweise unsicher konvertiert den Typ: von
a nach
b .
TatsÀchlich ist die Funktion magisch und weist den Compiler an, Daten vom Typ
a als Typ
b zu betrachten , ohne die Konsequenzen dieses Schritts zu berĂŒcksichtigen.
Es kann verwendet werden, um verschachtelte Typen zu konvertieren, aber Sie mĂŒssen sehr vorsichtig sein.
Im Jahr 2014 gab es eine Revolution mit
Newtype , nÀmlich eine sichere, ressourcenlose erzwungene Konvertierung!
import Data.Coerce(coerce) coerce :: Coercible ab => a -> b
Diese Funktion hat eine neue Ăra in der Arbeit mit
Newtype eingeleitet .
Coercible Forced Converter arbeitet mit Typen, die im Speicher dieselbe Struktur haben. Es sieht aus wie eine Typklasse, aber tatsÀchlich konvertiert der GHC Typen zur Kompilierungszeit und es ist nicht möglich, Instanzen selbst zu definieren.
Mit der Funktion
Data.Coerce.coerce können Sie Typen ohne Ressourcen konvertieren. Dazu benötigen wir jedoch Zugriff auf die
Typkonstruktoren .
Vereinfachen Sie nun unsere Funktion:
import Data.Semigroup import Data.Monoid import Control.Arrow import Data.Coerce aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = coerce . (foldMap (Just . Max &&& Sum))
Wir haben die Routine vermieden, verschachtelte Typen abzurufen, ohne Ressourcen mit nur einer Funktion zu verschwenden.
Rollen verschachtelter Datentypen
Mit der
Coerce- Funktion
können wir die Konvertierung verschachtelter Typen erzwingen.
Aber ist es notwendig, diese Funktion so weit zu nutzen?
Semantisch ist es absurd, von
Sorted (Down a) nach
Sorted a zu konvertieren.
Sie können jedoch versuchen:
ghci> let h = fromList2Sorted [1,2,3] :: Sorted Int ghci> let hDown = fromList2Sorted $ fmap Down [1,2,3] :: Sorted (Down Int) ghci> minView h Just (Down 1) ghci> minView (coerce h :: Sorted (Down Int)) Just (Down 1) ghci> minView hDown Just (Down 3)
Alles wÀre in Ordnung, aber die richtige Antwort ist
Just (Down 3) .
Um falsches Verhalten auszuschlieĂen, wurden nĂ€mlich Typrollen eingefĂŒhrt.
{-# LANGUAGE RoleAnnotations #-} type role Sorted nominal
Versuchen wir es jetzt:
ghci> minView (coerce h :: Sorted (Down Int)) error: Couldn't match type 'Int' with 'Down Int' arising from a use of 'coerce'
Deutlich besser!
Insgesamt gibt es 3 Rollen (
Typ Rolle ):
- reprÀsentativ - Àquivalent bei gleicher Darstellung
- nominal - muss genau den gleichen Typ haben
- Phantom - unabhÀngig von echten Inhalten. Entspricht allem
In den meisten FĂ€llen ist der Compiler intelligent genug, um die Rolle des Typs aufzudecken, aber es kann geholfen werden.
Verfeinertes Ableiten des Ableitungsverhaltens
Dank der Erweiterung der
DerivingVia- Sprache hat sich die Verteilungsrolle von
newtype verbessert.
Beginnend mit GHC 8.6, das kĂŒrzlich veröffentlicht wurde, ist diese neue Erweiterung erschienen.
{-# LANGUAGE DerivingVia #-} newtype Id = MkId Word deriving (Semigroup, Monoid) via Max Word
Wie Sie sehen können, wird das Typverhalten aufgrund der Verfeinerung der Ausgabe automatisch abgeleitet.
DerivingVia kann auf jeden Typ angewendet werden, der
Coercible unterstĂŒtzt und
vor allem - ganz ohne Ressourcenverbrauch!
DarĂŒber hinaus kann
DerivingVia nicht nur auf
neue Typen
angewendet werden , sondern auch auf alle isomorphen Typen, wenn sie
Generika- Generika und
Zwangskonvertierung mit Zwang unterstĂŒtzen.
Schlussfolgerungen
Types
newtype ist eine leistungsstarke Kraft, die Code erheblich vereinfacht und verbessert, Routine eliminiert und den Ressourcenverbrauch reduziert.
OriginalĂŒbersetzung :
Die GroĂmacht der Newtypes (Hiromi Ishii)PS Ich denke, nach diesem Artikel, der vor mehr als einem Jahr [nicht von meinem] Artikel veröffentlicht wurde, wird die Magie des Newtype in Haskell ĂŒber neue Typen etwas klarer!