我如何让 GHC 识别此代码段中的 SingI 实例?
How do I make the GHC recognize a SingI instance in this snippet?
有疑问
我有一个提升的数据类型 a
,我在其中适当地实例化了 data family Sing (a :: Foo)
。
我也有类型家族Bar (a :: Foo) (b :: Foo) :: Foo
在繁忙的功能中,我有:
(\(x :: Sing n) (y :: Sing m) -> doThingsWith (sing :: Sing (Bar n m)))
但我不确定我应该把 SingI
约束放在哪里。
更多详情,我的类型是
data PNat = NZ | NS PNat
data instance Sing (n :: PNat) where
SNZ :: Sing 'NZ
SNS :: Sing (m :: PNat) -> Sing ('NS m)
我的类型是
type family NPlus (n :: PNat) (m :: PNat) :: PNat where
NPlus NZ y = y
NPlus (NS x) y = NS (NPlus x y)
现在,我实际上能够手动写出显式单例改组:
nPlus :: forall n m nm. (NPlus n m ~ nm) => Sing n -> Sing m -> Sing nm
nPlus SNZ y = y
nPlus (SNS x) y = SNS (nPlus x y)
而且这个编译很好...我可以做到
\x y -> nPlus x y
但我觉得我应该可以在这里使用 singI
并让类型族完成这两项工作。此外,我也有很多这样的功能,并且希望不必每个都这样做。
我打开了 ScopedTypeVariables。
谢谢大家
编辑:啊,我刚刚意识到 SingI
实例不是自动派生的;我掀起了一个:
instance SingI NZ where
sing = SNZ
instance SingI n => SingI (NS n) where
sing = SNS sing
不幸的是,当我在上面的 lambda 中使用 sing
时,GHC 仍然告诉我没有 SingI
的实例:/
I feel like I should be able to just use singI here and let the type
family do both of the work.
这是不可能的。我们在 singletons
中拥有许多 TH 设施的原因是数据和函数定义必须在当前状态下重复。
惯用的用法是在术语级别定义一次所有内容,然后用 TH 派生其余部分。
-- LANGUAGE KitchenSink
import Data.Singletons.TH
import Data.Singletons.Prelude
$(singletons [d|
data PNat = NZ | NS PNat deriving (Eq, Show, Ord)
nPlus :: PNat -> PNat -> PNat
nPlus NZ y = y
nPlus (NS x) y = NS (nPlus x y) |])
这会创建 Sing
定义,nPlus
的类型族,SingI
实例,SingKind
实例,种类受限的 SPNat
类型同义词对于 Sing
,类型族和构造函数的去功能化符号,以及 Eq
和 Ord
以及 decidable equality 的类型级类似物。您可以点击模块上的 :bro
和 :i PNat
以准确找出生成的内容。
现在 nPlus
和类型族 NPlus
按预期工作。
提供一些关于 SingI
的解释:SingI a => t
等同于 Sing a -> t
。他们甚至编译成完全相同的核心代码。它们之间的唯一区别是 Sing
-s 是显式传递的,而 SingI
-s 是隐式传递的。 sing
提供从 SingI
到 Sing
的转换,而 singInstance
向后转换。
鉴于此,类似
(SingI (NPlus n m)) => Sing n -> Sing m -> Sing (NPlus n m)
很尴尬,因为它等同于
Sing (NPlus n m) -> Sing n -> Sing m -> Sing (NPlus n m)
可以实现为不做任何加法的常量函数。
那么我们什么时候应该使用SingI
或Sing
呢?最方便的方法是在 Sing
-s 上执行计算,因为我们可以对它们进行模式匹配,并且仅当我们只需要在某处插入或传递一个值时才使用 SingI
,但不要不需要模式匹配或递归。
我有一个提升的数据类型 a
,我在其中适当地实例化了 data family Sing (a :: Foo)
。
我也有类型家族Bar (a :: Foo) (b :: Foo) :: Foo
在繁忙的功能中,我有:
(\(x :: Sing n) (y :: Sing m) -> doThingsWith (sing :: Sing (Bar n m)))
但我不确定我应该把 SingI
约束放在哪里。
更多详情,我的类型是
data PNat = NZ | NS PNat
data instance Sing (n :: PNat) where
SNZ :: Sing 'NZ
SNS :: Sing (m :: PNat) -> Sing ('NS m)
我的类型是
type family NPlus (n :: PNat) (m :: PNat) :: PNat where
NPlus NZ y = y
NPlus (NS x) y = NS (NPlus x y)
现在,我实际上能够手动写出显式单例改组:
nPlus :: forall n m nm. (NPlus n m ~ nm) => Sing n -> Sing m -> Sing nm
nPlus SNZ y = y
nPlus (SNS x) y = SNS (nPlus x y)
而且这个编译很好...我可以做到
\x y -> nPlus x y
但我觉得我应该可以在这里使用 singI
并让类型族完成这两项工作。此外,我也有很多这样的功能,并且希望不必每个都这样做。
我打开了 ScopedTypeVariables。
谢谢大家
编辑:啊,我刚刚意识到 SingI
实例不是自动派生的;我掀起了一个:
instance SingI NZ where
sing = SNZ
instance SingI n => SingI (NS n) where
sing = SNS sing
不幸的是,当我在上面的 lambda 中使用 sing
时,GHC 仍然告诉我没有 SingI
的实例:/
I feel like I should be able to just use singI here and let the type family do both of the work.
这是不可能的。我们在 singletons
中拥有许多 TH 设施的原因是数据和函数定义必须在当前状态下重复。
惯用的用法是在术语级别定义一次所有内容,然后用 TH 派生其余部分。
-- LANGUAGE KitchenSink
import Data.Singletons.TH
import Data.Singletons.Prelude
$(singletons [d|
data PNat = NZ | NS PNat deriving (Eq, Show, Ord)
nPlus :: PNat -> PNat -> PNat
nPlus NZ y = y
nPlus (NS x) y = NS (nPlus x y) |])
这会创建 Sing
定义,nPlus
的类型族,SingI
实例,SingKind
实例,种类受限的 SPNat
类型同义词对于 Sing
,类型族和构造函数的去功能化符号,以及 Eq
和 Ord
以及 decidable equality 的类型级类似物。您可以点击模块上的 :bro
和 :i PNat
以准确找出生成的内容。
现在 nPlus
和类型族 NPlus
按预期工作。
提供一些关于 SingI
的解释:SingI a => t
等同于 Sing a -> t
。他们甚至编译成完全相同的核心代码。它们之间的唯一区别是 Sing
-s 是显式传递的,而 SingI
-s 是隐式传递的。 sing
提供从 SingI
到 Sing
的转换,而 singInstance
向后转换。
鉴于此,类似
(SingI (NPlus n m)) => Sing n -> Sing m -> Sing (NPlus n m)
很尴尬,因为它等同于
Sing (NPlus n m) -> Sing n -> Sing m -> Sing (NPlus n m)
可以实现为不做任何加法的常量函数。
那么我们什么时候应该使用SingI
或Sing
呢?最方便的方法是在 Sing
-s 上执行计算,因为我们可以对它们进行模式匹配,并且仅当我们只需要在某处插入或传递一个值时才使用 SingI
,但不要不需要模式匹配或递归。