У современных версий GHC есть какое-либо доказательство удаления?

22

Предположим, у меня есть параметр, который существует только для системы типов, например, как в этой маленькой программе:

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

Аргументы Proxy и члены в структуре действительно должны существовать только во время компиляции, чтобы помочь с проверкой типов при сохранении полиморфного MyPoly (в этом случае программа будет компилироваться без него, но этот надуманный пример является более общей проблемой, когда есть доказательства или прокси, которые нужны только во время компиляции) - существует только один конструктор для Proxy, и аргумент типа является фантомным типом.

Компиляция с помощью ghc с -ddump-stgпоказывает, что, по крайней мере, на этапе STG не стирается аргумент Proxy для конструктора или третий аргумент для конструктора.

Есть ли способ пометить их как только время компиляции, или иным образом помочь ghc выполнить стирание доказательства и исключить их?

a1kmm
источник

Ответы:

20

Действительно, ваш код приводит к тому, что Proxys сохраняется в конструкторе:

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

Однако с небольшими изменениями мы получаем желаемую оптимизацию. Нет больше Proxy!

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

Что я делал? Я сделал Proxyполе строгим :

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

В общем, мы не можем стереть нестрогие прокси из-за низов. Proxyи undefinedоба имеют тип, Proxy aно они не являются обсервационно эквивалентными, поэтому мы должны различать их во время выполнения.

Вместо этого строгий Proxyимеет только одно значение, поэтому GHC может его оптимизировать.

Тем не менее, нет аналогичной возможности оптимизировать (не-конструктор) параметр функции. Ваше поле (Proxy a -> a -> Int -> Int)потребует Proxyво время выполнения.

чи
источник
15

Есть два способа выполнить то, что вы хотите.

Несколько более старый способ - использовать Proxy # из GHC.Prim, который гарантированно будет удален во время компиляции.

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

Хотя это немного громоздко.

Другой способ - Proxyвообще отказаться от :

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

В настоящее время, у нас есть некоторые инструменты , которые облегчают работу без ProxyОбобщения , как AllowAmbiguousTypesи TypeApplications, например, означает , что вы можете применить тип вы имеете в виду непосредственно. Я не знаю, каков ваш вариант использования, но возьмите этот (надуманный) пример:

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"

Мы хотим прочитать, а затем показать значение некоторого типа, поэтому нам нужен способ указать, что это за тип. Вот как бы вы сделали это с расширениями:

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

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

>>> readShow @Int "01"
"1"
oisdk
источник
Последняя альтернатива (без прокси), на мой взгляд, лучшая.
Чи