Je suis sûr que tout le monde connaît les méthodes génériques du formulaire:
T DoSomething<T>(T item)
Cette fonction est également appelée paramétriquement polymorphe (PP), en particulier PP de rang 1 .
Disons que cette méthode peut être représentée à l'aide d'un objet fonction de la forme:
<T> : T -> T
Autrement dit, <T>
cela signifie qu'il prend un paramètre de type, et T -> T
signifie qu'il prend un paramètre de type T
et renvoie une valeur du même type.
Ensuite, ce serait une fonction PP de rang 2:
(<T> : T -> T) -> int
La fonction ne prend pas de paramètres de type elle-même, mais prend une fonction qui prend un paramètre de type. Vous pouvez continuer cela de manière itérative, rendant l'imbrication de plus en plus profonde, obtenant un PP de rang de plus en plus élevé.
Cette fonctionnalité est vraiment rare parmi les langages de programmation. Même Haskell ne le permet pas par défaut.
Est-ce utile? Peut-il décrire des comportements difficiles à décrire autrement?
Aussi, qu'est-ce que cela signifie pour quelque chose d' imprédicatif ? (dans ce contexte)
let sdff = (g : (f : <T> (e : T) => void) => void) => {}
Réponses:
En général, vous utilisez le polymorphisme de rang supérieur lorsque vous voulez que le callee pour pouvoir sélectionner la valeur d'un paramètre de type, plutôt que l' appelant . Par exemple:
N'importe quelle fonction
g
que je passe à cecif
doit pouvoir me donner un àInt
partir d'une valeur d'un certain type, où la seule chose quig
sait à propos de ce type est qu'il a une instance deShow
. Ce sont donc casher:Mais ce ne sont pas:
Une application particulièrement utile consiste à utiliser l'étendue des types pour appliquer l'étendue des valeurs . Supposons que nous ayons un objet de type
Action<T>
, représentant une action que nous pouvons exécuter pour produire un résultat de typeT
, tel qu'un futur ou un rappel.Supposons maintenant que nous avons également un
Action
qui peut allouer desResource<T>
objets:Nous voulons faire en sorte que ces ressources ne soient utilisées qu'à l'intérieur du lieu
Action
où elles ont été créées, et non partagées entre différentes actions ou différentes exécutions de la même action, afin que les actions soient déterministes et répétables.Nous pouvons utiliser des types de rang supérieur pour y parvenir en ajoutant un paramètre
S
aux typesResource
etAction
, qui est totalement abstrait - il représente la «portée» duAction
. Maintenant, nos signatures sont:Maintenant, quand nous donnons
runAction
unAction<S, T>
, nous sommes assurés que parce que le paramètre «scope»S
est entièrement polymorphe, il ne peut pas échapper au corps derunAction
- donc aucune valeur d'un type qui utiliseS
tel que leResource<S, int>
même ne peut s'échapper!(Dans Haskell, c'est ce qu'on appelle la
ST
monade, oùrunAction
est appeléerunST
,Resource
est appeléeSTRef
etnewResource
est appeléenewSTRef
.)la source
ST
monade est un exemple très intéressant. Pouvez-vous donner d'autres exemples de cas où un polymorphisme de rang supérieur serait utile?data Fetch d = forall a. Fetch (d a) (MVar a)
, qui est une paire de demande à une source de donnéesd
et un emplacement dans lequel stocker le résultat. Le résultat et le slot doivent avoir des types correspondants, mais ce type est masqué, vous pouvez donc avoir une liste hétérogène de requêtes vers la même source de données. Maintenant , vous pouvez utiliser le polymorphisme de rang supérieur pour écrire une fonction qui va chercher toutes les demandes, étant donné une fonction qui va chercher un:fetch :: (forall a. d a -> IO a) -> [Fetch d] -> IO ()
.Le polymorphisme de rang supérieur est extrêmement utile. Dans System F (le langage de base des langages FP typés que vous connaissez), cela est essentiel pour admettre les "encodages Church typés", qui est en fait la façon dont System F fait la programmation. Sans cela, le système F est complètement inutile.
Dans le système F, nous définissons les nombres comme
L'addition a le type
qui est un type de rang supérieur (le
forall c.
apparaît à l'intérieur de ces flèches).Cela se produit également ailleurs. Par exemple, si vous souhaitez indiquer qu'un calcul est un style de passage de continuation approprié (google "codensity haskell"), vous devez corriger cela comme
Même parler d'un type inhabité dans le système F nécessite un polymorphisme de rang supérieur
Le long et le court de cela, écrire une fonction dans un système de type pur (système F, CoC) nécessite un polymorphisme de rang supérieur si nous voulons traiter des données intéressantes.
Dans le système F en particulier, ces codages doivent être "imprédicatifs". Cela signifie qu'un
forall a.
quantifie absolument tous les types . Cela inclut de manière critique le type même que nous définissons. Dansforall a. a
cea
pourrait effectivement se présenter àforall a. a
nouveau! Dans des langages comme ML, ce n'est pas le cas, on dit qu'ils sont "prédictifs" puisqu'une variable de type quantifie uniquement sur l'ensemble des types sans quantificateurs (appelés monotypes). Notre définition de l'plus
imprédicativité requise aussi parce que nous avons instancié l'c
inl : Nat
êtreNat
!Enfin, je voudrais mentionner une dernière raison pour laquelle vous aimeriez à la fois l'imprédicativité et le polymorphisme de rang supérieur, même dans un langage avec des types arbitrairement récursifs (contrairement au système F). Dans Haskell, il existe une monade d'effets appelée "monade de threads d'état". L'idée est que la monade du thread d'état vous permet de muter les choses mais nécessite d'y échapper pour que votre résultat ne dépende de rien de mutable. Cela signifie que les calculs ST sont remarquablement purs. Pour appliquer cette exigence, nous utilisons un polymorphisme de rang supérieur
Ici, en veillant à ce qu'il
a
soit lié en dehors de la portée où nous introduisonss
, nous savons quea
représente un type bien formé qui ne dépend pass
. Nous utilisonss
pour paramétrer toutes les choses mutables dans ce thread d'état particulier afin que nous sachions que celaa
est indépendant des choses mutables et donc que rien n'échappe à la portée de ceST
calcul! Un merveilleux exemple d'utilisation de types pour exclure les programmes mal formés.Soit dit en passant, si vous souhaitez en savoir plus sur la théorie des types, je vous suggère d'investir dans un bon livre ou deux. Il est difficile d'apprendre ces trucs en morceaux. Je suggérerais l'un des livres de Pierce ou Harper sur la théorie du PL en général (et certains éléments de la théorie des types). Le livre "Sujets avancés en types et langages de programmation" couvre également une bonne partie de la théorie des types. Enfin, "Programmation dans la théorie des types de Martin Lof" est une très bonne exposition de la théorie des types intensionnelle que Martin Lof a décrite.
la source