Я попытаюсь представить здесь в «идиоматическое» singletons
решение (если такое вообще существует). Отборочные:
{-# LANGUAGE
RankNTypes, DataKinds, PolyKinds, ConstraintKinds, GADTs,
TypeOperators, MultiParamTypeClasses, TypeFamilies, ScopedTypeVariables #-}
import Data.Singletons.Prelude
import Data.Proxy
import GHC.Exts (Constraint)
-- SingI constraint here for simplicity's sake
class SingI name => Analyze (name :: Symbol) ty where
analyze :: Proxy name -> ty -> Int
data Rec rs where
Nil :: Rec '[]
Cons :: ty -> Rec rs -> Rec ('(name, ty) ': rs)
recName :: Rec ('(name, t) ': rs) -> Proxy name
recName _ = Proxy
Нам нужно All c rs
ограничения, но мы даем ему спину и сделать c
TyFun
вместо простого a -> Constraint
конструктора:
type family AllC (c :: TyFun a Constraint -> *) (rs :: [a]) :: Constraint where
AllC c '[] =()
AllC c (x ': xs) = (c @@ x, AllC c xs)
TyFun
позволяет нам абстрактный над конструкторами типа и типа семьи и дает нам частичное применение. Это дает нам почти первоклассные функции уровня типа с несколько уродливым синтаксисом. Обратите внимание, что мы обязательно теряем конструкторскую инъективность. @@
является оператором по применению TyFun
-s. TyFun a b -> *
означает, что a
- это вход, а b
- это выход, а конечный -> *
- это просто артефакт кодирования. С GHC 8.0 мы сможем только сделать
type a ~> b = TyFun a b -> *
И использовать a ~> b
в дальнейшем.
Мы можем реализовать теперь общую «классное» отображение над Rec
:
cMapRec ::
forall c rs r.
AllC c rs => Proxy c -> (forall name t. (c @@ '(name, t)) => Proxy name -> t -> r) -> Rec rs -> [r]
cMapRec p f Nil = []
cMapRec p f [email protected](Cons x xs) = f (recName r) x : cMapRec p f xs
Обратите внимание, что выше c
имеет вид TyFun (a, *) Constraint -> *
.
А затем реализовать analyzeRec
:
analyzeRec ::
forall c rs. (c ~ UncurrySym1 (TyCon2 Analyze))
=> AllC c rs => Rec rs -> [(String, Int)]
analyzeRec = cMapRec (Proxy :: Proxy c) (\p t -> (fromSing $ singByProxy p, analyze p t))
Во-первых, c ~ UncurrySym1 (TyCon2 Analyze)
это просто уровень типа let
связывания, что позволяет мне использовать c
в Proxy c
как стенографии. (Если бы я действительно хотел использовать все грязные трюки, я бы добавил {-# LANGUAGE PartialTypeSignatures #-}
и напишу Proxy :: _ c
).
UncurrySym1 (TyCon2 Analyze)
делает то же самое, что и uncurry Analyze
, если бы у Haskell была полная поддержка функций уровня. Очевидным преимуществом здесь является то, что мы можем выписать тип analyzeRec
на лету без дополнительных семейств или классов типа верхнего уровня, а также использовать AllC
в более общем плане.
В качестве бонуса, давайте удалить SingI
ограничение из Analyze
, и попытаться осуществить analyzeRec
.
class Analyze (name :: Symbol) ty where
analyze :: Proxy name -> ty -> Int
Теперь мы должны требовать дополнительное ограничение, которое выражает, что все name
-s в нашем Rec
являются SingI
.Мы можем использовать два cMapRec
-s и заархивировать результаты:
analyzeRec ::
forall analyze names rs.
(analyze ~ UncurrySym1 (TyCon2 Analyze),
names ~ (TyCon1 SingI :.$$$ FstSym0),
AllC analyze rs,
AllC names rs)
=> Rec rs -> [(String, Int)]
analyzeRec rc = zip
(cMapRec (Proxy :: Proxy names) (\p _ -> fromSing $ singByProxy p) rc)
(cMapRec (Proxy :: Proxy analyze) (\p t -> analyze p t) rc)
TyCon1 SingI :.$$$ FstSym0
Здесь можно перевести как SingI . fst
.
Это все еще примерно в пределах уровня абстракции, который может быть легко выражено с TyFun
-s. Главным ограничением, конечно же, является отсутствие лямбда. В идеале нам не нужно было бы использовать zip
, вместо этого мы использовали бы AllC (\(name, t) -> (SingI name, Analyze name t))
и использовали один cMapRec
. С singletons
, если мы не сможем использовать крыло с беспроблемным программированием на уровне шрифта, мы должны ввести новое точечное семейство типов.
Занятно, GHC 8,0 будет достаточно сильным, чтобы мы могли реализовать лямбды типа уровня с нуля, хотя это будет, наверное, некрасиво, как ад. Например, \p -> (SingI (fst p), uncurry Analyze p)
может выглядеть примерно так:
Eval (
Lam "p" $
PairL :@@
(LCon1 SingI :@@ (FstL :@@ Var "p")) :@@
(UncurryL :@@ LCon2 Analyze :@@ Var "p"))
где все L
postfixes обозначают термин лямбда вложения обычных TyFun
-s (пока другой коллекции будет сокращения от порожденную TH ...).
У меня есть prototype, хотя он работает только с еще более уродливыми переменными de Bruijn, из-за ошибки GHC. В нем также есть Fix
и явная лень на уровне типа.
Удивительный! Я только что вычислил что-то вроде «AllC» и застрял в понимании функциональных символов. Я думаю, что смогу сделать эту работу. Я чувствую, что переход на полный маршрут cMap слишком далеко, но это почти возможно! –
Не стесняйтесь спрашивать о 'TyFun'-s. Они вовлекают много синтаксического шума, и это, вероятно, стоит экспозиции. Кроме того, вы можете посмотреть [это] (https://typesandkinds.wordpress.com/2013/04/01/defunctionalization-for-the-win/), если вы еще не прочитали его. –
Я снял этот пост некоторое время назад, но я думаю, что теперь у меня есть фон, чтобы лучше понять его, еще раз спасибо за ссылку! –