Держи мое пиво.
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ConstraintKinds #-}
import Debug.Trace
data Dict c where Dict :: c => Dict c
-- An isomorphism between explicit dictionary-passing style (Dict c -> a)
-- and typeclass constraints (c => a) exists:
from :: (c => a) -> (Dict c -> a)
from v Dict = v
to :: (Dict c -> a) -> (c => a)
to f = f Dict
data Translation = Trans { m :: forall a . Floating a => a }
f1, f2 :: Dict (Floating a) -> a -> a
f1 = trace "hello" $ \Dict x -> x - 2.0
f2 = \Dict -> trace "hello" $ \x -> x - 2.0
main = do
let trans1 = Trans { m = to (flip f1 1.5) }
trans2 = Trans { m = to (flip f2 1.5) }
putStrLn "trans1"
print (m trans1)
print (m trans1)
putStrLn "trans2"
print (m trans2)
print (m trans2)
Уделите секунду, чтобы предсказать, что это выдаст, прежде чем запустить его.Затем спросите вашего GHC, согласна ли она с вашим предположением.
Очистить как грязь?
Основное различие, которое вам нужно провести здесь, здесь, в этом значительно упрощенном примере:
> g = trace "a" $ \() -> trace "b" ()
> g ()
a
b
()
> g ()
b
()
Существует отдельное понятие кэширования функции и кэширования ее выходных данных .Последнее просто никогда не выполняется в GHC (хотя см. Обсуждение того, что происходит с вашей оптимизированной версией ниже).Первое может показаться глупым, но на самом деле это не так глупо, как вы думаете;Вы можете представить себе написание функции, которая, скажем, id
, если гипотеза Коллатца верна, и not
в противном случае.В такой ситуации имеет смысл только один раз проверить гипотезу Коллатца, а затем кэшировать, будем ли мы вести себя как id
или not
навсегда.
Как только вы поймете этот основной факт, следующийВы должны поверить, что в GHC ограничения класса типов скомпилированы в функции.(Аргументами функции являются словари классов типов, рассказывающие о том, как ведут себя все методы классов типов.) Сам GHC управляет созданием и передачей этих словарей за вас, и в большинстве случаев он довольно прозрачен для пользователя.
НоРезультатом этой стратегии компиляции является: полиморфный , но тип с ограничением по типу - это функция , даже если в ней нет стрелок функций .То есть
f 1.5 :: Floating a => a
выглядит как обычное старое значение;но на самом деле это функция , которая принимает словарь Floating a
и выдает значение типа a
.Таким образом, любые вычисления, которые входят в вычисление значения a
, переделывают заново каждый раз, когда эта функция применяется (читай: используется для определенного мономорфного типа), потому что, в конце концов, выбранное точное значение критически зависит от того, какметоды класса типов ведут себя.
Это оставляет только вопрос о том, почему оптимизации изменили ситуацию в вашей ситуации.Там, я полагаю, то, что произошло, называется «специализацией», в которой компилятор попытается заметить, когда полиморфные вещи используются со статически известным мономорфным типом, и сделать для этого обязательную привязку.Это выглядит примерно так:
-- starting point
main = do
let trans = \dict -> trace "hello" $ minus dict (fromRational dict (3%2)) (fromRational dict (2%1))
print (trans dictForDouble)
print (trans dictForDouble)
-- specialization
main = do
let trans = \dict -> trace "hello" $ minus dict (fromRational dict (3%2)) (fromRational dict (2%1))
let transForDouble = trans dictForDouble
print transForDouble
print transForDouble
-- inlining
main = do
let transForDouble = trace "hello" $ minus dictForDouble (fromRational dict (3%2)) (fromRational dictForDouble (2%1))
print transForDouble
print transForDouble
В этом последнем случае функция исчезла;это «как если бы» GHC кэшировал вывод trans
при применении к словарю dictForDouble
.(Если вы скомпилируете с оптимизацией и -ddump-simpl
, вы увидите, что это идет еще дальше, делая постоянное распространение, чтобы превратить материал minus ...
в D# -0.5##
. Вот так!)