Apakah versi GHC modern memiliki penghapusan bukti?

22

Misalkan saya memiliki parameter yang hanya ada untuk kepentingan sistem tipe, misalnya seperti dalam program kecil ini:

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

Argumen Proxy dan anggota dalam struktur hanya benar-benar perlu ada pada waktu kompilasi untuk membantu memeriksa jenis sambil mempertahankan polimorfik MyPoly (dalam hal ini, program akan dikompilasi tanpa itu, tetapi contoh yang dibuat ini adalah masalah yang lebih umum di mana ada bukti atau proksi yang hanya diperlukan pada waktu kompilasi) - hanya ada satu konstruktor untuk Proksi, dan argumen tipe adalah tipe hantu.

Kompilasi dengan ghc dengan -ddump-stgmenunjukkan bahwa setidaknya pada tahap STG, tidak ada penghapusan argumen Proxy ke konstruktor atau argumen ketiga ke konstruktor.

Apakah ada cara untuk menandai ini sebagai waktu kompilasi saja, atau sebaliknya membantu ghc untuk melakukan penghapusan bukti dan mengecualikannya?

a1kmm
sumber

Jawaban:

20

Memang, kode Anda menyebabkan Proxydisimpan di konstruktor:

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

Namun, dengan perubahan kecil, kami mendapatkan optimasi yang diinginkan. Tidak lagi Proxy!

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

Apa yang saya lakukan? Saya membuat Proxybidang yang ketat :

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

Secara umum, kami tidak dapat menghapus proksi yang tidak ketat karena bawah. Proxydan undefinedkeduanya bertipe Proxy atetapi tidak setara secara observasi, jadi kita harus membedakannya saat runtime.

Sebagai gantinya, ketat Proxyhanya memiliki satu nilai, sehingga GHC dapat mengoptimalkannya.

Namun, tidak ada fitur serupa untuk mengoptimalkan parameter fungsi (non-konstruktor). Bidang Anda (Proxy a -> a -> Int -> Int)akan membutuhkan waktu Proxyruntime.

chi
sumber
15

Ada dua cara untuk mencapai apa yang Anda inginkan.

Cara yang sedikit lebih tua adalah menggunakan Proxy # dari GHC.Prim, yang dijamin akan terhapus pada waktu kompilasi.

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

Meskipun ini sedikit rumit.

Cara lain adalah melupakan Proxysemuanya:

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

Saat ini, kami memiliki beberapa alat yang membuatnya lebih mudah untuk bekerja tanpa Proxy: ekstensi suka AllowAmbiguousTypesdan TypeApplications, misalnya, berarti Anda dapat menerapkan jenis yang Anda maksud secara langsung. Saya tidak tahu apa use case Anda, tetapi ambil contoh ini (dibuat-buat):

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"

Kami ingin membaca dan kemudian menunjukkan nilai dari beberapa jenis, jadi kami perlu cara untuk menunjukkan apa jenis yang sebenarnya. Begini cara Anda melakukannya dengan ekstensi:

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

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

>>> readShow @Int "01"
"1"
oisdk
sumber
Alternatif terakhir (tanpa proksi) adalah yang terbaik, menurut saya.
chi