La absurd
fonction dans Data.Void
a la signature suivante, où Void
est le type logiquement inhabité exporté par ce package:
-- | Since 'Void' values logically don't exist, this witnesses the logical
-- reasoning tool of \"ex falso quodlibet\".
absurd :: Void -> a
Je connais assez de logique pour obtenir la remarque de la documentation que cela correspond, par la correspondance des propositions en tant que types, à la formule valide ⊥ → a
.
Ce qui me rend perplexe et curieux, c'est: dans quel genre de problèmes de programmation pratiques cette fonction est-elle utile? Je pense que c'est peut-être utile dans certains cas en tant que moyen sûr de traiter de manière exhaustive les cas "impossible à réaliser", mais je n'en sais pas assez sur les utilisations pratiques de Curry-Howard pour dire si cette idée est sur le bonne voie du tout.
EDIT: Exemples de préférence en Haskell, mais si quelqu'un veut utiliser un langage typé de manière dépendante, je ne vais pas me plaindre ...
la source
absurd
fonction a été utilisée dans cet article traitant de laCont
monade: haskellforall.com/2012/12/the-continuation-monad.htmlabsurd
comme une direction de l'isomorphisme entreVoid
etforall a. a
.Réponses:
La vie est un peu dure, puisque Haskell n'est pas strict. Le cas d'utilisation général est de gérer les chemins impossibles. Par exemple
Cela s'avère quelque peu utile. Considérez un type simple pour
Pipes
il s'agit d'une version stricte et simplifiée du type de tuyaux standard de la
Pipes
bibliothèque de Gabriel Gonzales . Maintenant, nous pouvons encoder un tube qui ne cède jamais (c'est-à-dire un consommateur) commecela ne cède vraiment jamais. L'implication de ceci est que la règle de pliage appropriée pour a
Consumer
estou bien, que vous pouvez ignorer le cas de rendement lorsque vous traitez avec les consommateurs. C'est la version générale de ce modèle de conception: utilisez des types de données polymorphes et
Void
éliminez les possibilités lorsque vous en avez besoin.L'utilisation la plus classique de
Void
est probablement dans CPS.autrement dit, a
Continuation
est une fonction qui ne revient jamais.Continuation
est la version type de «non». De cela, nous obtenons une monade de CPS (correspondant à la logique classique)puisque Haskell est pur, nous ne pouvons rien obtenir de ce type.
la source
Void
est inhabité. Dans Haskell, il contient_|_
. Dans un langage strict, un constructeur de données qui prend un argument de typeVoid
ne peut jamais être appliqué, donc le côté droit de la correspondance de modèle est inaccessible. Dans Haskell, vous devez utiliser un!
pour appliquer cela, et GHC ne remarquera probablement pas que le chemin est inaccessible._|_
? et souffre-t-il alors de la même limitation?Considérez cette représentation pour les termes lambda paramétrés par leurs variables libres. (Voir les articles de Bellegarde et Hook 1994, Bird et Paterson 1999, Altenkirch et Reus 1999.)
Vous pouvez certainement en faire un
Functor
, capturer la notion de changement de nom etMonad
capturer la notion de substitution.Considérons maintenant les termes fermés : ce sont les habitants de
Tm Void
. Vous devriez pouvoir intégrer les termes fermés dans des termes avec des variables libres arbitraires. Comment?Le hic, bien sûr, est que cette fonction traversera le terme sans rien faire. Mais c'est une touche plus honnête que
unsafeCoerce
. Et c'est pourquoi avacuous
été ajouté àData.Void
...Ou écrivez un évaluateur. Voici les valeurs avec des variables libres dans
b
.Je viens de représenter les lambdas comme des fermetures. L'évaluateur est paramétré par un environnement mappant les variables libres
a
vers les valeurs overb
.Tu l'as deviné. Pour évaluer un terme fermé à n'importe quelle cible
Plus généralement,
Void
est rarement utilisé seul, mais est pratique lorsque vous voulez instancier un paramètre de type d'une manière qui indique une sorte d'impossibilité (par exemple, ici, en utilisant une variable libre dans un terme fermé). Souvent , ces types paramétrés viennent avec des fonctions d'ordre supérieur des opérations de levage sur les paramètres des opérations du type entier (par exemple, ici,fmap
,>>=
,eval
). Vous passez doncabsurd
comme opération à usage généralVoid
.Pour un autre exemple, imaginez utiliser
Either e v
pour capturer des calculs qui, espérons-le, vous donneront unv
mais pourraient soulever une exception de typee
. Vous pouvez utiliser cette approche pour documenter uniformément le risque de mauvais comportement. Pour des calculs parfaitement bien comportés dans ce cadre, preneze
pour êtreVoid
, puis utilisezpour courir en toute sécurité ou
pour intégrer des composants sûrs dans un monde dangereux.
Oh, et un dernier hourra, gérer un "impossible". Il apparaît dans la construction générique de la fermeture à glissière, partout où le curseur ne peut pas être.
J'ai décidé de ne pas supprimer le reste, même si ce n'est pas tout à fait pertinent.
En fait, c'est peut-être pertinent. Si vous vous sentez aventureux, cet article inachevé montre comment utiliser
Void
pour compresser la représentation des termes avec des variables libresdans n'importe quelle syntaxe générée librement à partir d'un foncteur
Differentiable
et . Nous utilisons pour représenter des régions sans variables libres et pour représenter des tubes qui traversent des régions sans variables libres, soit vers une variable libre isolée, soit vers une jonction dans les chemins vers deux ou plusieurs variables libres. Doit finir cet article un jour.Traversable
f
Term f Void
[D f (Term f Void)]
Pour un type sans valeurs (ou du moins, aucune digne de parler en compagnie polie),
Void
est remarquablement utile. Etabsurd
c'est comment vous l'utilisez.la source
forall f. vacuous f = unsafeCoerce f
ce une règle de réécriture GHC valide?Functor
instances peuvent être des GADT qui ne ressemblent en fait pas à des foncteurs.Functor
n'enfreindraient pas lafmap id = id
règle? Ou est-ce ce que vous entendez par «faux» ici?C'est exactement vrai.
Vous pourriez dire que ce
absurd
n'est pas plus utile queconst (error "Impossible")
. Cependant, il est de type restreint, de sorte que sa seule entrée peut être quelque chose de typeVoid
, un type de données qui est intentionnellement laissé inhabité. Cela signifie qu'il n'y a pas de valeur réelle à laquelle vous pouvez passerabsurd
. Si jamais vous vous retrouvez dans une branche de code où le vérificateur de type pense que vous avez accès à quelque chose de typeVoid
, alors, eh bien, vous êtes dans une situation absurde . Donc, vous utilisez simplementabsurd
pour marquer que cette branche de code ne doit jamais être atteinte.«Ex falso quodlibet» signifie littéralement «à partir [d'une] fausse [proposition], tout suit». Ainsi, lorsque vous constatez que vous détenez une donnée dont le type est
Void
, vous savez que vous avez entre les mains de fausses preuves. Vous pouvez donc remplir n'importe quel trou que vous voulez (viaabsurd
), car à partir d'une fausse proposition, tout suit.J'ai écrit un article de blog sur les idées derrière Conduit qui a un exemple d'utilisation
absurd
.http://unknownparallel.wordpress.com/2012/07/30/pipes-to-conduits-part-6-leftovers/#running-a-pipeline
la source
Généralement, vous pouvez l'utiliser pour éviter les correspondances de motifs apparemment partielles. Par exemple, saisir une approximation des déclarations de type de données à partir de cette réponse :
Ensuite, vous pouvez utiliser
absurd
comme ceci, par exemple:la source
Il existe différentes manières de représenter le type de données vide . L'un est un type de données algébrique vide. Une autre façon est d'en faire un alias pour ∀α.α ou
dans Haskell - c'est ainsi que nous pouvons l'encoder dans le système F (voir le chapitre 11 des preuves et types ). Ces deux descriptions sont bien sûr isomorphes et l'isomorphisme est observé par
\x -> x :: (forall a.a) -> Void
et parabsurd :: Void -> a
.Dans certains cas, nous préférons la variante explicite, généralement si le type de données vide apparaît dans un argument d'une fonction, ou dans un type de données plus complexe, comme dans Data.Conduit :
Dans certains cas, nous préférons la variante polymorphe, généralement le type de données vide est impliqué dans le type de retour d'une fonction.
absurd
se produit lorsque nous convertissons entre ces deux représentations.Par exemple,
callcc :: ((a -> m b) -> m a) -> m a
uses (implicite)forall b
. Cela peut aussi être de type((a -> m Void) -> m a) -> m a
, car un appel à la contination ne revient pas réellement, il transfère le contrôle à un autre point. Si nous voulions travailler avec des suites, nous pourrions définir(Nous pourrions utiliser
type Continuation' r a = forall b . a -> Cont r b
mais cela nécessiterait des types de rang 2.) Et puis,vacuousM
convertit celaCont r Void
enCont r b
.(Notez également que vous pouvez utiliser haskellers.com pour rechercher l'utilisation (dépendances inversées) d'un certain package, comme pour voir qui et comment utilise le package void .)
la source
TypeApplications
peut être utilisé pour être plus explicite sur les détails deproof :: (forall a. a) -> Void
:proof fls = fls @Void
.Dans les langages à typage dépendant comme Idris, c'est probablement plus utile que dans Haskell. En règle générale, dans une fonction totale, lorsque le modèle correspond à une valeur qui ne peut pas être déplacée dans la fonction, vous construisez alors une valeur de type inhabité et utilisez
absurd
pour finaliser la définition de cas.Par exemple, cette fonction supprime un élément d'une liste avec la contrainte de coût au niveau du type qui y est présente:
Où le second cas dit qu'il y a un certain élément dans une liste vide, ce qui est bien absurde. En général, cependant, le compilateur ne le sait pas et nous devons souvent être explicites. Ensuite, le compilateur peut vérifier que la définition de la fonction n'est pas partielle et nous obtenons des garanties de compilation plus fortes.
À travers le point de vue de Curry-Howard, où sont les propositions, alors
absurd
est une sorte de QED dans une preuve par contradiction.la source