Selon cette question , le système de type de Scala est Turing complet . Quelles ressources sont disponibles pour permettre à un nouveau venu de profiter de la puissance de la programmation au niveau du type?
Voici les ressources que j'ai trouvées jusqu'à présent:
- La grande sorcellerie de Daniel Spiewak au pays de Scala
- Programmation au niveau du type d'Apocalisp dans Scala
- HList de Jesper
Ces ressources sont excellentes, mais j'ai l'impression que je manque les bases et que je n'ai donc pas de base solide sur laquelle construire. Par exemple, où existe-t-il une introduction aux définitions de type? Quelles opérations puis-je effectuer sur les types?
Existe-t-il de bonnes ressources d'introduction?
Réponses:
Aperçu
La programmation au niveau du type présente de nombreuses similitudes avec la programmation traditionnelle au niveau de la valeur. Cependant, contrairement à la programmation au niveau de la valeur, où le calcul se produit au moment de l'exécution, dans la programmation au niveau du type, le calcul se produit au moment de la compilation. Je vais essayer de faire des parallèles entre la programmation au niveau de la valeur et la programmation au niveau du type.
Paradigmes
Il existe deux principaux paradigmes dans la programmation au niveau du type: "orienté objet" et "fonctionnel". La plupart des exemples liés à partir d'ici suivent le paradigme orienté objet.
Un bon exemple assez simple de programmation au niveau du type dans le paradigme orienté objet peut être trouvé dans l' implémentation par apocalisp du calcul lambda , reproduite ici:
Comme on peut le voir dans l'exemple, le paradigme orienté objet pour la programmation au niveau du type se déroule comme suit:
trait Lambda
ce que les garanties qui existent les types suivants:subst
,apply
eteval
.trait App extends Lambda
paramétrés avec deux types (S
etT
, les deux doivent être des sous-types deLambda
),trait Lam extends Lambda
paramétrés avec un type (T
) ettrait X extends Lambda
(qui n'est pas paramétré).#
(qui est très similaire à l'opérateur point:.
pour les valeurs). En traitApp
de l'exemple de calcul lambda, le typeeval
est mis en œuvre comme suit:type eval = S#eval#apply[T]
. Il s'agit essentiellement d'appeler leeval
type du paramètre du traitS
et d'appeler leapply
paramètre avecT
le résultat. Remarque, ilS
est garanti d'avoir uneval
type car le paramètre spécifie qu'il s'agit d'un sous-type deLambda
. De même, le résultat deeval
doit avoir unapply
type, car il est spécifié comme étant un sous-type deLambda
, comme spécifié dans le trait abstraitLambda
.Le paradigme fonctionnel consiste à définir de nombreux constructeurs de types paramétrés qui ne sont pas regroupés en traits.
Comparaison entre la programmation au niveau de la valeur et la programmation au niveau du type
abstract class C { val x }
trait C { type X }
C.x
(référençant la valeur du champ / la fonction x dans l'objet C)C#x
(référençant le type de champ x dans le trait C)def f(x:X) : Y
type f[x <: X] <: Y
(cela s'appelle un "constructeur de type" et se produit généralement dans le trait abstrait)def f(x:X) : Y = x
type f[x <: X] = x
a:A == b:B
implicitly[A =:= B]
assert(a == b)
implicitly[A =:= B]
A <:< B
, compile uniquement siA
est un sous-type deB
A =:= B
, compile uniquement siA
est un sous-type deB
etB
est un sous-type deA
A <%< B
, ("visible en tant que") compile uniquement siA
est visible en tant queB
(c'est-à-dire qu'il y a une conversion implicite deA
en un sous-type deB
)Conversion entre types et valeurs
Dans de nombreux exemples, les types définis via des traits sont souvent à la fois abstraits et scellés, et ne peuvent donc pas être instanciés directement ou via une sous-classe anonyme. Il est donc courant d'utiliser
null
comme valeur d'espace réservé lors d'un calcul au niveau de la valeur en utilisant un type d'intérêt:val x:A = null
, oùA
est le type qui vous tient à cœurEn raison de l'effacement de type, les types paramétrés se ressemblent tous. De plus, (comme mentionné ci-dessus) les valeurs avec
null
lesquelles vous travaillez ont tendance à être toutes , et donc conditionner le type d'objet (par exemple via une déclaration de correspondance) est inefficace.L'astuce consiste à utiliser des fonctions et des valeurs implicites. Le cas de base est généralement une valeur implicite et le cas récursif est généralement une fonction implicite. En effet, la programmation au niveau du type fait un usage intensif des implicits.
Considérez cet exemple ( tiré de metascala et apocalisp ):
Ici vous avez un encodage peano des nombres naturels. Autrement dit, vous avez un type pour chaque entier non négatif: un type spécial pour 0, à savoir
_0
; et chaque entier supérieur à zéro a un type de la formeSucc[A]
, oùA
est le type représentant un entier plus petit. Par exemple, le type représentant 2 serait:Succ[Succ[_0]]
(successeur appliqué deux fois au type représentant zéro).Nous pouvons alias différents nombres naturels pour une référence plus pratique. Exemple:
(C'est un peu comme définir un
val
comme le résultat d'une fonction.)Maintenant, supposons que nous voulions définir une fonction au niveau de la valeur
def toInt[T <: Nat](v : T)
qui prend une valeur d'argument,,v
qui se conformeNat
et renvoie un entier représentant le nombre naturel encodé dansv
le type de. Par exemple, si nous avons la valeurval x:_3 = null
(null
de typeSucc[Succ[Succ[_0]]]
), nous voudrionstoInt(x)
retourner3
.Pour l'implémenter
toInt
, nous allons utiliser la classe suivante:Comme nous le verrons ci-dessous, il y aura un objet construit à partir de la classe
TypeToValue
pour chacunNat
de_0
jusqu'à (par exemple)_3
, et chacun stockera la représentation de la valeur du type correspondant (c'estTypeToValue[_0, Int]
-à- dire stockera la valeur0
,TypeToValue[Succ[_0], Int]
stockera la valeur1
, etc.). Remarque,TypeToValue
est paramétré par deux types:T
etVT
.T
correspond au type auquel nous essayons d'attribuer des valeurs (dans notre exemple,Nat
) etVT
correspond au type de valeur que nous lui attribuons (dans notre exemple,Int
).Maintenant, nous faisons les deux définitions implicites suivantes:
Et nous implémentons
toInt
comme suit:Pour comprendre comment
toInt
fonctionne, considérons ce qu'il fait sur quelques entrées:Lorsque nous appelons
toInt(z)
, le compilateur recherche un argument implicitettv
de typeTypeToValue[_0, Int]
(puisquez
est de type_0
). Il trouve l'objet_0ToInt
, il appelle lagetValue
méthode de cet objet et le récupère0
. Le point important à noter est que nous n'avons pas spécifié au programme quel objet utiliser, le compilateur l'a trouvé implicitement.Voyons maintenant
toInt(y)
. Cette fois, le compilateur recherche un argument implicitettv
de typeTypeToValue[Succ[_0], Int]
(puisquey
est de typeSucc[_0]
). Il trouve la fonctionsuccToInt
, qui peut renvoyer un objet du type approprié (TypeToValue[Succ[_0], Int]
) et l'évalue. Cette fonction elle-même prend un argument implicite (v
) de typeTypeToValue[_0, Int]
(c'est-à-dire, aTypeToValue
où le premier paramètre de type en a un de moinsSucc[_]
). Le compilateur fournit_0ToInt
(comme cela a été fait dans l'évaluationtoInt(z)
ci - dessus) etsuccToInt
construit un nouvelTypeToValue
objet avec une valeur1
. Encore une fois, il est important de noter que le compilateur fournit toutes ces valeurs implicitement, car nous n'y avons pas accès explicitement.Vérifier votre travail
Il existe plusieurs façons de vérifier que vos calculs au niveau du type font ce que vous attendez. Voici quelques approches. Faites en sorte que deux types
A
etB
que vous souhaitez vérifier soient égaux. Vérifiez ensuite que la compilation suivante:Equal[A, B]
Equal[T1 >: T2 <: T2, T2]
( extrait d'apocolisp )implicitly[A =:= B]
Vous pouvez également convertir le type en valeur (comme indiqué ci-dessus) et effectuer une vérification à l'exécution des valeurs. Par exemple
assert(toInt(a) == toInt(b))
, oùa
est de typeA
etb
est de typeB
.Ressources supplémentaires
L'ensemble des constructions disponibles se trouve dans la section des types du manuel de référence scala (pdf) .
Adriaan Moors a plusieurs articles académiques sur les constructeurs de types et des sujets connexes avec des exemples de scala:
Apocalisp est un blog avec de nombreux exemples de programmation au niveau du type dans scala.
ScalaZ est un projet très actif qui fournit des fonctionnalités qui étendent l'API Scala à l'aide de diverses fonctionnalités de programmation au niveau du type. C'est un projet très intéressant qui a un grand succès.
MetaScala est une bibliothèque de niveau type pour Scala, comprenant des méta-types pour les nombres naturels, les booléens, les unités, HList, etc. C'est un projet de Jesper Nordenberg (son blog) .
Le Michid (blog) a quelques exemples impressionnants de la programmation au niveau du type à Scala (d'autre réponse):
Debasish Ghosh (blog) a également des articles pertinents:
(J'ai fait des recherches sur ce sujet et voici ce que j'ai appris. Je suis encore nouveau dans ce domaine, veuillez donc signaler toute inexactitude dans cette réponse.)
la source
En plus des autres liens ici, il y a aussi mes articles de blog sur la méta-programmation au niveau du type dans Scala:
la source
Comme suggéré sur Twitter: Shapeless: Une exploration de la programmation générique / polytypique dans Scala par Miles Sabin.
la source
la source
Scalaz a du code source, un wiki et des exemples.
la source