Чи є в сучасних версіях 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

Аргументи проксі і учасники в структурі дійсно повинні існувати лише в час компіляції, щоб допомогти з перевіркою типів, зберігаючи поліморфний MyPoly (у цьому випадку програма буде компілювати без цього, але цей надуманий приклад є більш загальною проблемою, коли є докази чи проксі, які потрібні лише під час компіляції) - для проксі існує лише один конструктор, а аргумент типу - фантомний тип.

Компіляція з ghc з -ddump-stgпоказує, що принаймні на етапі STG не відбувається стирання аргументу проксі для конструктора або третього аргументу до конструктора.

Чи є якийсь спосіб відзначити їх як час компіляції або іншим чином допомогти GHC зробити стирання доказів та виключити їх?

Відповіді:


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"

Остання альтернатива (без проксі) - найкраща, на мою думку.
чі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.