Haben moderne GHC-Versionen irgendeine Art von Proof-Löschung?

22

Angenommen, ich habe einen Parameter, der nur zum Nutzen des Typsystems existiert, zum Beispiel wie in diesem kleinen Programm:

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

Die Proxy-Argumente und -Mitglieder in der Struktur müssen nur zur Kompilierungszeit vorhanden sein, um bei der Typprüfung zu helfen, während das polymorphe MyPoly beibehalten wird (in diesem Fall wird das Programm ohne es kompiliert, aber dieses erfundene Beispiel ist ein allgemeineres Problem, wenn es solche gibt Proofs oder Proxys, die nur zur Kompilierungszeit benötigt werden) - Es gibt nur einen Konstruktor für Proxy, und das Argument type ist ein Phantomtyp.

Das Kompilieren mit ghc mit -ddump-stgzeigt, dass zumindest in der STG-Phase das Proxy-Argument für den Konstruktor oder das dritte Argument für den Konstruktor nicht gelöscht wird .

Gibt es eine Möglichkeit, diese als nur zur Kompilierungszeit zu markieren oder auf andere Weise ghc dabei zu helfen, das Löschen von Beweisen durchzuführen und sie auszuschließen?

a1kmm
quelle

Antworten:

20

In der Tat führt Ihr Code dazu, dass Proxys im Konstruktor gespeichert werden:

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

Mit einer kleinen Änderung erhalten wir jedoch die gewünschte Optimierung. Nicht mehr Proxy!

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

Was habe ich getan? Ich habe das ProxyFeld streng gemacht :

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

Im Allgemeinen können wir nicht strenge Proxys aufgrund von Bottoms nicht löschen. Proxyund undefinedsind beide vom Typ, Proxy aaber sie sind nicht beobachtungsäquivalent, daher müssen wir sie zur Laufzeit unterscheiden.

Stattdessen hat ein Strict Proxynur einen Wert, sodass GHC diesen weg optimieren kann.

Es gibt jedoch keine ähnliche Funktion, um einen (Nicht-Konstruktor-) Funktionsparameter zu optimieren. Ihr Feld (Proxy a -> a -> Int -> Int)benötigt Proxyzur Laufzeit eine.

Chi
quelle
15

Es gibt zwei Möglichkeiten, um das zu erreichen, was Sie wollen.

Die etwas ältere Methode ist die Verwendung von Proxy # von GHC.Prim, das beim Kompilieren garantiert gelöscht wird.

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

Obwohl dies etwas umständlich ist.

Der andere Weg ist, auf das ProxyGanze zu verzichten :

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

Heutzutage haben wir einige Tools, die das Arbeiten ohne erleichtern Proxy: Erweiterungen wie AllowAmbiguousTypesund TypeApplicationsbedeuten beispielsweise, dass Sie den Typ, den Sie meinen, direkt anwenden können. Ich weiß nicht, was Ihr Anwendungsfall ist, aber nehmen Sie dieses (erfundene) Beispiel:

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"

Wir möchten einen Wert eines Typs lesen und dann anzeigen, daher benötigen wir eine Möglichkeit, den tatsächlichen Typ anzugeben. So würden Sie es mit Erweiterungen machen:

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

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

>>> readShow @Int "01"
"1"
oisdk
quelle
Die letzte Alternative (keine Proxies) ist meiner Meinung nach die beste.
Chi