Le moderne versioni di GHC hanno qualche tipo di cancellazione della prova?


22

Supponiamo che io abbia un parametro che esiste solo a beneficio del sistema di tipi, ad esempio come in questo piccolo programma:

{-# 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

Gli argomenti proxy e i membri nella struttura devono davvero esistere solo al momento della compilazione per aiutare con il controllo del tipo mantenendo il polimero MyPoly (in questo caso, il programma verrà compilato senza di esso, ma questo esempio inventato è un problema più generale in cui ci sono prove o proxy che sono necessari solo al momento della compilazione) - esiste solo un costruttore per Proxy e l'argomento type è un tipo fantasma.

Compilare con ghc con -ddump-stgmostra che almeno nella fase STG, non c'è cancellazione dell'argomento Proxy per il costruttore o del terzo argomento per il costruttore.

Esiste un modo per contrassegnarli come solo in fase di compilazione, o altrimenti aiutare Ghc a fare la cancellazione della prova ed escluderli?

Risposte:


20

Infatti, il tuo codice porta a Proxys essere memorizzato nel costruttore:

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

Tuttavia, con una piccola modifica, otteniamo l'ottimizzazione desiderata. Non più Proxy!

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

Cosa ho fatto? Ho reso il Proxycampo rigoroso :

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

In generale, non possiamo cancellare i proxy non rigorosi a causa dei fondi. Proxye undefinedsono entrambi di tipo Proxy ama non sono equivalenti dal punto di vista osservazionale, quindi dobbiamo distinguerli in fase di esecuzione.

Invece, uno stretto Proxyha un solo valore, quindi GHC può ottimizzarlo.

Tuttavia, non esiste una funzione simile per ottimizzare un parametro di funzione (non costruttore). Il tuo campo (Proxy a -> a -> Int -> Int)richiederà un Proxyruntime.


15

Esistono due modi per ottenere ciò che desideri.

Il modo leggermente più vecchio è usare Proxy # da GHC.Prim, che è garantito per essere cancellato in fase di compilazione.

{-# 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 (\_ -> (+))]

Anche se questo è un po 'ingombrante.

L'altro modo è rinunciare del Proxytutto:

{-# 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

Al giorno d'oggi, abbiamo alcuni strumenti che semplificano il lavoro senza Proxy: estensioni come AllowAmbiguousTypese TypeApplications, ad esempio, significano che puoi applicare il tipo che intendi direttamente. Non so quale sia il tuo caso d'uso, ma prendo questo esempio (inventato):

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"

Vogliamo leggere e quindi mostrare un valore di qualche tipo, quindi abbiamo bisogno di un modo per indicare quale sia il tipo effettivo. Ecco come lo faresti con le estensioni:

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

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

>>> readShow @Int "01"
"1"

L'ultima alternativa (nessun proxy) è la migliore, secondo me.
Chi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.