Ответ 1
Здесь пример того, как один проект const-math-ghc-plugin использует ImpredicativeTypes
для указания списка совпадающих правил.
Идея состоит в том, что когда мы имеем выражение вида App (PrimOp nameStr) (Lit litVal)
, мы хотим найти соответствующее правило, основанное на имени primop. A litVal
будет либо a MachFloat d
, либо MachDouble d
(d
является Rational
). Если мы найдем правило, мы хотим применить функцию для этого правила к d
, преобразованному в правильный тип.
Функция mkUnaryCollapseIEEE
делает это для унарных функций.
mkUnaryCollapseIEEE :: (forall a. RealFloat a => (a -> a))
-> Opts
-> CoreExpr
-> CoreM CoreExpr
mkUnaryCollapseIEEE fnE opts [email protected](App f1 (App f2 (Lit lit)))
| isDHash f2, MachDouble d <- lit = e d mkDoubleLitDouble
| isFHash f2, MachFloat d <- lit = e d mkFloatLitFloat
where
e d = evalUnaryIEEE opts fnE f1 f2 d expr
Первый аргумент должен иметь тип Rank-2, потому что он будет создан в Float
или Double
в зависимости от конструктора литерала. Список правил выглядит следующим образом:
unarySubIEEE :: String -> (forall a. RealFloat a => a -> a) -> CMSub
unarySubIEEE nm fn = CMSub nm (mkUnaryCollapseIEEE fn)
subs =
[ unarySubIEEE "GHC.Float.exp" exp
, unarySubIEEE "GHC.Float.log" log
, unarySubIEEE "GHC.Float.sqrt" sqrt
-- lines omitted
, unarySubIEEE "GHC.Float.atanh" atanh
]
Это нормально, если слишком много шаблонов для моего вкуса.
Однако существует аналогичная функция mkUnaryCollapsePrimIEEE
. В этом случае правила различны для разных версий GHC. Если мы хотим поддерживать несколько GHC, это становится немного сложнее. Если бы мы приняли тот же подход, определение subs
потребовало бы много CPP, что может быть недостижимым. Вместо этого мы определили правила в отдельном файле для каждой версии GHC. Однако mkUnaryCollapsePrimIEEE
недоступен в этих модулях из-за циклических проблем с импортом. Возможно, мы могли бы переструктурировать модули, чтобы они работали, но вместо этого мы определили набор правил как:
unaryPrimRules :: [(String, (forall a. RealFloat a => a -> a))]
unaryPrimRules =
[ ("GHC.Prim.expDouble#" , exp)
, ("GHC.Prim.logDouble#" , log)
-- lines omitted
, ("GHC.Prim.expFloat#" , exp)
, ("GHC.Prim.logFloat#" , log)
]
Используя ImpredicativeTypes
, мы можем сохранить список функций Rank-2, готовый для использования для первого аргумента mkUnaryCollapsePrimIEEE
. Альтернативами было бы намного больше CPP/шаблона, изменение структуры модуля (или кругового импорта) или много дублирования кода. Ничего из того, что я хотел бы.
Кажется, я помню штаб-квартиру GHC, указав, что они хотели бы отказаться от поддержки расширения, но, возможно, они пересмотрели. Это очень полезно время от времени.