Les versions GHC modernes ont-elles une sorte d'effacement de preuve?

22

Supposons que j'ai un paramètre qui existe uniquement pour le bénéfice du système de type, par exemple comme dans ce petit programme:

{-# LANGUAGE GADTs #-}
module Main where
import Data.Proxy
import Data.List

data MyPoly where
  MyConstr :: Proxy a -> a -> (Proxy a -> a -> Int -> Int) -> MyPoly

listOfPolys :: [MyPoly]
listOfPolys = [MyConstr Proxy 5 (const (+))
              , MyConstr Proxy 10 (const (+))
              , MyConstr Proxy 15 (const (+))]

main = print $ foldl' (\v (MyConstr p n a) -> a p n v) 0 listOfPolys

Les arguments et les membres du proxy dans la structure ne doivent vraiment exister qu'au moment de la compilation pour aider à la vérification de type tout en conservant le polymorphe MyPoly (dans ce cas, le programme se compilera sans lui, mais cet exemple artificiel est un problème plus général où il y a les preuves ou les proxys qui ne sont nécessaires qu'au moment de la compilation) - il n'y a qu'un seul constructeur pour Proxy, et l'argument type est un type fantôme.

La compilation avec ghc avec -ddump-stgmontre qu'au moins au stade STG, il n'y a pas d'effacement de l'argument Proxy au constructeur ou du troisième argument au constructeur.

Existe-t-il un moyen de les marquer comme étant à la compilation uniquement, ou autrement aider ghc à effacer les preuves et à les exclure?

a1kmm
la source

Réponses:

20

En effet, votre code conduit au Proxystockage de s dans le constructeur:

ProxyOpt.listOfPolys8 :: ProxyOpt.MyPoly
[GblId, Caf=NoCafRefs, Unf=OtherCon []] =
    CCS_DONT_CARE ProxyOpt.MyConstr! [Data.Proxy.Proxy
                                      ProxyOpt.listOfPolys9
                                      ProxyOpt.listOfPolys4];

Cependant, avec un petit changement, nous obtenons l'optimisation souhaitée. Pas plus Proxy!

ProxyOpt.listOfPolys8 :: ProxyOpt.MyPoly
[GblId, Caf=NoCafRefs, Unf=OtherCon []] =
    CCS_DONT_CARE ProxyOpt.MyConstr! [ProxyOpt.listOfPolys9
                                      ProxyOpt.listOfPolys4];

Qu'est-ce que j'ai fait? J'ai rendu le Proxychamp strict :

data MyPoly where
  MyConstr :: !(Proxy a) -> a -> (Proxy a -> a -> Int -> Int) -> MyPoly
           -- ^ --

En général, nous ne pouvons pas effacer les procurations non strictes à cause des fonds. Proxyet undefinedsont tous les deux de type Proxy amais ils ne sont pas équivalents sur le plan de l'observation, nous devons donc les distinguer au moment de l'exécution.

Au lieu de cela, un strict Proxyn'a qu'une seule valeur, donc GHC peut l'optimiser.

Il n'y a cependant pas de fonctionnalité similaire pour optimiser un paramètre de fonction (non constructeur). Votre champ (Proxy a -> a -> Int -> Int)nécessitera un Proxyau moment de l'exécution.

chi
la source
15

Il y a deux façons d'accomplir ce que vous voulez.

La méthode légèrement plus ancienne consiste à utiliser le Proxy # de GHC.Prim, qui est garanti d'être effacé au moment de la compilation.

{-# LANGUAGE GADTs, MagicHash #-}
module Main where

import Data.List
import GHC.Prim

data MyPoly where
  MyConstr :: Proxy# a -> a -> (Proxy# a -> a -> Int -> Int) -> MyPoly

listOfPolys :: [MyPoly]
listOfPolys = [MyConstr proxy# 5 (\_ -> (+))
              , MyConstr proxy# 10 (\_ -> (+))
              , MyConstr proxy# 15 (\_ -> (+))]

Bien que ce soit un peu lourd.

L'autre façon est de renoncer au Proxytout:

{-# LANGUAGE GADTs #-}

module Main where

import Data.List

data MyPoly where
  MyConstr :: a -> (a -> Int -> Int) -> MyPoly

listOfPolys :: [MyPoly]
listOfPolys = [ MyConstr 5  (+)
              , MyConstr 10 (+)
              , MyConstr 15 (+)
              ]

main = print $ foldl' (\v (MyConstr n a) -> a n v) 0 listOfPolys

De nos jours, nous avons quelques outils qui facilitent le travail sans Proxy: des extensions comme AllowAmbiguousTypeset TypeApplications, par exemple, signifient que vous pouvez appliquer directement le type que vous voulez dire. Je ne sais pas quel est votre cas d'utilisation, mais prenez cet exemple (artificiel):

import Data.Proxy

asTypeP :: a -> Proxy a -> a
asTypeP x _ = x

readShow :: (Read a, Show a) => Proxy a -> String -> String
readShow p x = show (read x `asTypeP` p)

>>> readShow (Proxy :: Proxy Int) "01"
"1"

Nous voulons lire puis afficher une valeur d'un certain type, nous avons donc besoin d'un moyen d'indiquer quel est le type réel. Voici comment vous le feriez avec des extensions:

{-# LANGUAGE AllowAmbiguousTypes, TypeApplications, ScopedTypeVariables #-}

readShow :: forall a. (Read a, Show a) => String -> String
readShow x = show (read x :: a)

>>> readShow @Int "01"
"1"
oisdk
la source
La dernière alternative (pas de procuration) est la meilleure, à mon avis.
chi