Newtype est une déclaration de type de données spécialisée. Telle qu'elle ne contient qu'un constructeur et un champ.
newtype Foo a = Bar a newtype Id = MkId Word

Questions typiques pour les débutants
Quelle est la différence avec les données de type de données? data Foo a = Bar a data Id = MkId Word
La principale spécificité de
newtype est qu'il se compose des mêmes parties que son seul champ. Plus précisément, il diffère de l'original au niveau du type, mais a la même représentation en mémoire, et est calculé strictement (pas paresseusement).
En bref -
les nouveaux
types sont plus efficaces en raison de leur présentation.
Oui, cela ne signifie rien pour moi ... je vais utiliser des donnéesNon, enfin, vous pouvez toujours activer l'extension
-funpack-strict-fields :) pour les
champs stricts (pas paresseux) ou spécifier directement
data Id = MkId !Word
Pourtant, la puissance de
newtype ne se limite pas à l'efficacité de calcul. Ils sont beaucoup plus forts!
3 rôles newtype

Masquer la mise en œuvre
module Data.Id (Id()) where newtype Id = MkId Word
newtype diffère de l'original, en interne uniquement
Word .
Mais nous
masquons le constructeur
MkId en dehors du module.
Mise en œuvre mise en œuvre
{-# LANGUAGE GeneralizedNewtypeDeriving #-} newtype Id = MkId Word deriving (Num, Eq)
Bien que cela ne soit pas dans la norme Haskell2010, en développant la sortie de newTypes génériques, vous pouvez automatiquement déduire le comportement de
newtype de la même manière que le comportement du champ interne. Dans notre cas, le comportement de
Eq Id et
Num Id est le même que
Eq Word et
Num Word .
Beaucoup plus peut être réalisé grâce à l'expansion de l'élevage raffiné (
DerivingVia ), mais plus à ce sujet plus tard.
Mise en œuvre du choix
Malgré son propre constructeur, dans certains cas, vous pouvez utiliser votre représentation interne.
Défi
Il y a une liste d'entiers. Trouvez le montant maximum et total en un seul passage sur la liste.
Et n'utilisez pas de
plis et de
plis .
Réponse typique
Bien sûr,
pliez ! :)
foldr :: Foldable t => (a -> b -> b) -> b -> ta -> b
Et, la fonction finale est décrite comme ceci:
aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = foldr (\el (m, s) -> (Just el `max` m, el + s)) (Nothing, 0)
Si vous regardez attentivement, vous pouvez voir des opérations similaires des deux côtés:
juste el `max` m et
el + s . Dans les deux cas, cartographie et opération binaire. Et les éléments vides sont
Nothing et
0 .
Oui, ce sont des monoides!
Monoïde et semi-groupe plus en détailUn semi-groupe est une propriété d'une opération binaire associative
x ⋄ (y ⋄ z) == (x ⋄ y) ⋄ z
Un monoïde est une propriété d'une opération associative (c'est-à-dire un semi-groupe)
x ⋄ (y ⋄ z) == (x ⋄ y) ⋄ z
qui a un élément vide qui ne change aucun élément ni à droite ni à gauche
x ⋄ empty == x == empty ⋄ x
Max et
(+) sont associatifs, les deux ont des éléments vides -
rien et
0 .
Et la combinaison de la cartographie des monoïdes avec la convolution est
pliable !
Pliable plus détailléRappelons la définition du pliage:
class Foldable t where foldMap :: (Monoid m) => (a -> m) -> ta -> m ...
Appliquons le comportement de pliage à
max et
(+) . Nous ne pouvons pas organiser plus d'une implémentation du monoïde
Word . Il est temps de profiter de l'implémentation du choix
newtype !
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
Il faut faire une remarque.
Le fait est que pour être monoïde pour le type de données
Max , nous avons besoin d'un élément minimal, c'est-à-dire pour qu'un élément vide existe. Ainsi, seul un
Max a limité peut être un monoïde.
Monoïde d'élément maximal théoriquement correct newtype Max a = Max a instance Ord a => Semigroup (Max a) instance Bounded a => Monoid (Max a)
Donc, d'une manière ou d'une autre, nous devrons convertir notre type de données afin qu'un élément vide apparaisse et que nous puissions utiliser la coagulation.
L'élément conjugué
Maybe transforme un semi-groupe en un monoïde!
Libéralisation des restrictions dans les versions récentes du GHCDe retour dans GHC 8.2, une contrainte de type monoïde était requise
instance Monoid a => Monoid (Maybe a)
ce qui signifie que nous avions besoin d'un autre nouveau type:
Et c'est déjà beaucoup plus simple dans GHC 8.4, où seul un semi-groupe en restriction de type est nécessaire, et même il n'est pas nécessaire de créer un type d'option.
instance Semigroup a => Monoid (Maybe a)
Réponse pliante
Eh bien, mettez à jour le code en utilisant la pliabilité et les flèches.
Nous rappelons que (.) Est juste une composition fonctionnelle:
(.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (gx)
Et rappelez-vous que
fmap est un foncteur:
fmap :: Functor f => (a -> b) -> fa -> fb
et son implémentation pour
Maybe est décrite un peu plus haut.
Flèche plus détailléeLes flèches sont des propriétés de certaines fonctions qui vous permettent de travailler avec elles dans un diagramme.
Plus de détails peuvent être trouvés ici:
Flèches: une interface générale pour le calculDans notre cas, nous utilisons la fonction Arrows
C’est
instance Arrow (->)
Nous utiliserons les fonctions:
(***) :: Arrow a => abc -> ab' c' -> a (b, b') (c, c') (&&&) :: Arrow a => abc -> abc' -> ab (c, c')
Pour notre cas
abc == (->) bc == b -> c
Et, en conséquence, la signature de nos fonctions se réduit à:
(***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c')) (&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
Ou en termes assez simples, la fonction
(***) combine deux fonctions avec un argument (et un type de sortie) en une fonction avec le travail d'une paire d'arguments à l'entrée et à la sortie, respectivement, une paire de types de sortie.
La fonction
(&&&) est une version allégée
(***) , où le type des arguments d'entrée des deux fonctions est le même, et à l'entrée nous n'avons pas une paire d'arguments, mais un argument.
Au total, la fonction unificatrice a acquis la forme:
import Data.Semigroup import Data.Monoid import Control.Arrow aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = (fmap getMax *** getSum) . (foldMap (Just . Max &&& Sum))
Cela s'est avéré très brièvement!
Mais, il est toujours fatigant d'encapsuler et d'inverser les données des types imbriqués!
Vous pouvez le réduire davantage et une conversion forcée sans ressources nous aidera!
Rôles de conversion forcée et de type sécurisés et sans ressources
Il y a une fonction du paquet
Unsafe.Coerce -
unsafeCoerce import Unsafe.Coerce(unsafeCoerce) unsafeCoerce :: a -> b
La fonction forcée non sécurisée convertit le type: de
a en
b .
En fait, la fonction est magique, elle indique au compilateur de considérer les données de type
a comme de type
b , sans prendre en compte les conséquences de cette étape.
Il peut être utilisé pour convertir des types imbriqués, mais vous devez être très prudent.
En 2014, il y a eu une révolution avec le nouveau type, à savoir une conversion forcée sûre et sans ressources!
import Data.Coerce(coerce) coerce :: Coercible ab => a -> b
Cette fonctionnalité a ouvert une nouvelle ère en travaillant avec
newtype .
Coercible Forced Converter fonctionne avec des types qui ont la même structure en mémoire. Elle ressemble à une classe de type, mais en fait le GHC convertit les types au moment de la compilation et il n'est pas possible de définir des instances par lui-même.
La fonction
Data.Coerce.coerce vous permet de convertir des types sans ressources, mais pour cela, nous devons avoir accès aux constructeurs de types.
Simplifions maintenant notre fonction:
import Data.Semigroup import Data.Monoid import Control.Arrow import Data.Coerce aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = coerce . (foldMap (Just . Max &&& Sum))
Nous avons évité la routine de tirer des types imbriqués; nous l'avons fait sans gaspiller les ressources avec une seule fonction.
Rôles des types de données imbriqués
Avec la fonction de
coercition, nous pouvons forcer la conversion de tous les types imbriqués.
Mais est-il nécessaire d'utiliser si largement cette fonctionnalité?
Sémantiquement, il est absurde de convertir en
Sorted a de
Sorted (Down a) .
Cependant, vous pouvez essayer:
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)
Tout irait bien, mais la bonne réponse est
Just (Down 3) .
À savoir, afin de supprimer les comportements incorrects, des rôles de type ont été introduits.
{-# LANGUAGE RoleAnnotations #-} type role Sorted nominal
Essayons maintenant:
ghci> minView (coerce h :: Sorted (Down Int)) error: Couldn't match type 'Int' with 'Down Int' arising from a use of 'coerce'
Beaucoup mieux!
Au total, il y a 3 rôles (
type rôle ):
- représentation - équivalent si la même représentation
- nominal - doit avoir exactement le même type
- fantôme - indépendant du contenu réel. Équivalent à n'importe quoi
Dans la plupart des cas, le compilateur est suffisamment intelligent pour révéler le rôle du type, mais il peut être utile.
Dérivation raffinée DerivingVia Behavior
Grâce à l'expansion du langage
DerivingVia , le rôle de distribution de
newtype s'est amélioré.
À partir de GHC 8.6, qui a été récemment publié, cette nouvelle extension est apparue.
{-# LANGUAGE DerivingVia #-} newtype Id = MkId Word deriving (Semigroup, Monoid) via Max Word
Comme vous pouvez le voir, le comportement du type est automatiquement déduit en raison du raffinement de la sortie.
DerivingVia peut être appliqué à n'importe quel type qui prend en charge
Coercible et
surtout - complètement sans consommation de ressources!
De plus,
DerivingVia peut être appliqué non seulement à
newtype , mais également à tous les types isomorphes s'ils prennent en charge les
génériques génériques et la conversion forcée
coercitive .
Conclusions
Types
newtype est une force puissante qui simplifie et améliore considérablement le code, élimine la routine et réduit la consommation de ressources.
Traduction originale :
La grande puissance des nouveaux types (Hiromi Ishii)PS Je pense qu'après cet article, publié il y a plus d'un an [pas par mon] article, la magie du newtype dans Haskell sur les nouveaux types deviendra un peu plus claire!