Czy współczesne wersje GHC mają jakikolwiek dowód na usunięcie?

22

Załóżmy, że mam parametr, który istnieje tylko na korzyść systemu typów, na przykład jak w tym małym programie:

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

Argumenty i elementy proxy w strukturze muszą naprawdę istnieć tylko w czasie kompilacji, aby pomóc w sprawdzaniu typu przy zachowaniu polimorficznej MyPoly (w tym przypadku program skompiluje się bez niego, ale ten wymyślony przykład jest bardziej ogólnym problemem, w którym występują proofy lub proxy, które są potrzebne tylko w czasie kompilacji) - dla Proxy istnieje tylko jeden konstruktor, a argumentem typu jest typ fantomowy.

Kompilacja z ghc z -ddump-stgpokazuje, że przynajmniej na etapie STG nie ma usunięcia argumentu Proxy dla konstruktora ani trzeciego argumentu dla konstruktora.

Czy jest jakiś sposób, aby oznaczyć je jako czas kompilacji, lub w inny sposób pomóc ghc w skasowaniu dowodu i wykluczyć je?

a1kmm
źródło

Odpowiedzi:

20

Rzeczywiście, twój kod powoduje, że Proxys jest przechowywany w konstruktorze:

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

Jednak przy niewielkiej zmianie uzyskujemy pożądaną optymalizację. Nigdy więcej Proxy!

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

Co ja zrobiłem? Zrobiłem Proxypole ścisłe :

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

Zasadniczo nie możemy usuwać nieprecyzyjnych serwerów proxy z powodu dna. Proxyi undefinedoba są typu, Proxy aale nie są równoważne obserwacyjnie, więc musimy je rozróżnić w czasie wykonywania.

Zamiast tego ścisły Proxyma tylko jedną wartość, więc GHC może to zoptymalizować.

Jednak nie ma podobnej funkcji do optymalizacji parametru funkcji (niebędącej konstruktorem). Twoje pole (Proxy a -> a -> Int -> Int)będzie wymagać Proxyw czasie wykonywania.

chi
źródło
15

Istnieją dwa sposoby osiągnięcia tego, co chcesz.

Nieco starszym sposobem jest użycie Proxy # z GHC.Prim, który z pewnością zostanie wymazany podczas kompilacji.

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

Chociaż jest to trochę kłopotliwe.

Innym sposobem jest Proxycałkowite zaniechanie :

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

Obecnie mamy kilka narzędzi, które ułatwiają pracę bez Proxy: rozszerzenia takie jak AllowAmbiguousTypesi TypeApplications, na przykład, oznaczają, że możesz zastosować typ, który masz na myśli bezpośrednio. Nie wiem, jaki jest twój przypadek użycia, ale weźmy ten (wymyślony) przykład:

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"

Chcemy przeczytać, a następnie pokazać wartość pewnego typu, dlatego potrzebujemy sposobu, aby wskazać, jaki jest rzeczywisty typ. Oto, jak zrobiłbyś to z rozszerzeniami:

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

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

>>> readShow @Int "01"
"1"
oisdk
źródło
Moim zdaniem ostatnia alternatywa (bez serwerów proxy) jest najlepsza.
chi