禁用 Haskell 类型强制
Disable Haskell type coercion
我有基于 hyperloglog. I'm trying to parametrize my Container
with size, and use reflection 的示例,可以在容器的函数中使用此参数。
import Data.Proxy
import Data.Reflection
newtype Container p = Container { runContainer :: [Int] }
deriving (Eq, Show)
instance Reifies p Integer => Monoid (Container p) where
mempty = Container $ replicate (fromIntegral (reflect (Proxy :: Proxy p))) 0
mappend (Container l) (Container r) = undefined
我蹩脚的 Monoid 实例根据具体化参数定义了 mempty
,并做了一些 "type-safe" mappend
。当我尝试对不同大小的容器求和时,它工作得很好,但因类型错误而失败。
但是它仍然可以被 coerce
欺骗,我正在寻找在编译时阻止它的方法:
ghci> :set -XDataKinds
ghci> :m +Data.Coerce
ghci> let c3 = mempty :: Container 3
ghci> c3
ghci> Container {runContaner: [0,0,0]}
ghci> let c4 = coerce c3 :: Container 4
ghci> :t c4
ghci> c4 :: Container 4
ghci> c4
ghci> Container {runContainer: [0,0,0]}
添加类型角色没有帮助
type role Container nominal
问题在于,只要构造函数在范围内,newtype
s 就可以强制转换为它们的表示 - 事实上,这是 Coercible
的很大一部分动机。 Coercible
约束与任何其他类型的 class 约束一样,会自动为您搜索并放在一起,甚至更是如此。因此,coerce c3
发现您有
instance Coercible (Container p) [Int]
instance Coercible [Int] (Container p')
对于所有 p
和 p'
,并愉快地通过
为您构建复合强制
instance (Coercible a b, Coercible b c) => Coercible a c
如果您不导出 Container
构造函数 – 无论如何您可能都想这样做! – 然后它不再知道 newtype
等于它的表示(你失去了上面的前两个实例),并且你在其他模块中得到了期望的错误:
ContainerClient.hs:13:6:
Couldn't match type ‘4’ with ‘3’
arising from trying to show that the representations of
‘Container 3’ and
‘Container 4’ are the same
Relevant role signatures: type role Container nominal nominal
In the expression: coerce c3
In an equation for ‘c4’: c4 = coerce c3
但是,您始终可以在定义 newtype
的模块中打破不变量(通过 coerce
或其他方式)。
附带说明一下,您可能不想在这里使用记录式访问器并将其导出;允许用户使用记录更新语法从你下面更改代码,所以
c3 :: Container 3
c3 = mempty
c3' :: Container 3
c3' = c3{runContainer = []}
生效。使 runContainer
成为一个独立的函数。
我们可以通过查看核心(通过 -ddump-simpl
)来验证我们正在获得两个 newtype
表示约束的组合:在定义 Container
的模块中(我也称之为 Container
),输出是(如果我们删除导出列表)
c4 :: Container 4
[GblId, Str=DmdType]
c4 =
c3
`cast` (Container.NTCo:Container[0] <GHC.TypeLits.Nat>_N <3>_N
; Sym (Container.NTCo:Container[0] <GHC.TypeLits.Nat>_N <4>_N)
:: Container 3 ~R# Container 4)
读起来有点困难,但重要的是 Container.NTCo:Container[0]
:NTCo
是 newtype
[=37= 之间的 newtype
强制转换] 及其表示类型。 Sym
反过来,;
组成两个约束。
调用最终约束[=40=];那么整个打字推导是
Sym :: (a ~ b) -> (b ~ a)
-- Sym is built-in
(;) :: (a ~ b) -> (b ~ c) -> (a ~ c)
-- (;) is built-in
γₙ :: k -> (p :: k) -> Container p ~ [Int]
γₙ k p = Container.NTCo:Container[0] <k>_N <p>_N
γ₃ :: Container 3 ~ [Int]
γ₃ = γₙ GHC.TypeLits.Nat 3
γ₄ :: Container 4 ~ [Int]
γ₄ = γₙ GHC.TypeLits.Nat 4
γ₄′ :: [Int] ~ Container 4
γ₄′ = Sym γ₄
γₓ :: Container 3 ~ Container 4
γₓ = γ₃ ; γ₄′
以下是我使用的源文件:
Container.hs:
{-# LANGUAGE FlexibleContexts, UndecidableInstances, ScopedTypeVariables,
RoleAnnotations, PolyKinds, DataKinds #-}
module Container (Container(), runContainer) where
import Data.Proxy
import Data.Reflection
import Data.Coerce
newtype Container p = Container { runContainer :: [Int] }
deriving (Eq, Show)
type role Container nominal
instance Reifies p Integer => Monoid (Container p) where
mempty = Container $ replicate (fromIntegral (reflect (Proxy :: Proxy p))) 0
mappend (Container l) (Container r) = Container $ l ++ r
c3 :: Container 3
c3 = mempty
-- Works
c4 :: Container 4
c4 = coerce c3
ContainerClient.hs:
{-# LANGUAGE DataKinds #-}
module ContainerClient where
import Container
import Data.Coerce
c3 :: Container 3
c3 = mempty
-- Doesn't work :-)
c4 :: Container 4
c4 = coerce c3
我有基于 hyperloglog. I'm trying to parametrize my Container
with size, and use reflection 的示例,可以在容器的函数中使用此参数。
import Data.Proxy
import Data.Reflection
newtype Container p = Container { runContainer :: [Int] }
deriving (Eq, Show)
instance Reifies p Integer => Monoid (Container p) where
mempty = Container $ replicate (fromIntegral (reflect (Proxy :: Proxy p))) 0
mappend (Container l) (Container r) = undefined
我蹩脚的 Monoid 实例根据具体化参数定义了 mempty
,并做了一些 "type-safe" mappend
。当我尝试对不同大小的容器求和时,它工作得很好,但因类型错误而失败。
但是它仍然可以被 coerce
欺骗,我正在寻找在编译时阻止它的方法:
ghci> :set -XDataKinds
ghci> :m +Data.Coerce
ghci> let c3 = mempty :: Container 3
ghci> c3
ghci> Container {runContaner: [0,0,0]}
ghci> let c4 = coerce c3 :: Container 4
ghci> :t c4
ghci> c4 :: Container 4
ghci> c4
ghci> Container {runContainer: [0,0,0]}
添加类型角色没有帮助
type role Container nominal
问题在于,只要构造函数在范围内,newtype
s 就可以强制转换为它们的表示 - 事实上,这是 Coercible
的很大一部分动机。 Coercible
约束与任何其他类型的 class 约束一样,会自动为您搜索并放在一起,甚至更是如此。因此,coerce c3
发现您有
instance Coercible (Container p) [Int]
instance Coercible [Int] (Container p')
对于所有 p
和 p'
,并愉快地通过
instance (Coercible a b, Coercible b c) => Coercible a c
如果您不导出 Container
构造函数 – 无论如何您可能都想这样做! – 然后它不再知道 newtype
等于它的表示(你失去了上面的前两个实例),并且你在其他模块中得到了期望的错误:
ContainerClient.hs:13:6:
Couldn't match type ‘4’ with ‘3’
arising from trying to show that the representations of
‘Container 3’ and
‘Container 4’ are the same
Relevant role signatures: type role Container nominal nominal
In the expression: coerce c3
In an equation for ‘c4’: c4 = coerce c3
但是,您始终可以在定义 newtype
的模块中打破不变量(通过 coerce
或其他方式)。
附带说明一下,您可能不想在这里使用记录式访问器并将其导出;允许用户使用记录更新语法从你下面更改代码,所以
c3 :: Container 3
c3 = mempty
c3' :: Container 3
c3' = c3{runContainer = []}
生效。使 runContainer
成为一个独立的函数。
我们可以通过查看核心(通过 -ddump-simpl
)来验证我们正在获得两个 newtype
表示约束的组合:在定义 Container
的模块中(我也称之为 Container
),输出是(如果我们删除导出列表)
c4 :: Container 4
[GblId, Str=DmdType]
c4 =
c3
`cast` (Container.NTCo:Container[0] <GHC.TypeLits.Nat>_N <3>_N
; Sym (Container.NTCo:Container[0] <GHC.TypeLits.Nat>_N <4>_N)
:: Container 3 ~R# Container 4)
读起来有点困难,但重要的是 Container.NTCo:Container[0]
:NTCo
是 newtype
[=37= 之间的 newtype
强制转换] 及其表示类型。 Sym
反过来,;
组成两个约束。
调用最终约束[=40=];那么整个打字推导是
Sym :: (a ~ b) -> (b ~ a)
-- Sym is built-in
(;) :: (a ~ b) -> (b ~ c) -> (a ~ c)
-- (;) is built-in
γₙ :: k -> (p :: k) -> Container p ~ [Int]
γₙ k p = Container.NTCo:Container[0] <k>_N <p>_N
γ₃ :: Container 3 ~ [Int]
γ₃ = γₙ GHC.TypeLits.Nat 3
γ₄ :: Container 4 ~ [Int]
γ₄ = γₙ GHC.TypeLits.Nat 4
γ₄′ :: [Int] ~ Container 4
γ₄′ = Sym γ₄
γₓ :: Container 3 ~ Container 4
γₓ = γ₃ ; γ₄′
以下是我使用的源文件:
Container.hs:
{-# LANGUAGE FlexibleContexts, UndecidableInstances, ScopedTypeVariables,
RoleAnnotations, PolyKinds, DataKinds #-}
module Container (Container(), runContainer) where
import Data.Proxy
import Data.Reflection
import Data.Coerce
newtype Container p = Container { runContainer :: [Int] }
deriving (Eq, Show)
type role Container nominal
instance Reifies p Integer => Monoid (Container p) where
mempty = Container $ replicate (fromIntegral (reflect (Proxy :: Proxy p))) 0
mappend (Container l) (Container r) = Container $ l ++ r
c3 :: Container 3
c3 = mempty
-- Works
c4 :: Container 4
c4 = coerce c3
ContainerClient.hs:
{-# LANGUAGE DataKinds #-}
module ContainerClient where
import Container
import Data.Coerce
c3 :: Container 3
c3 = mempty
-- Doesn't work :-)
c4 :: Container 4
c4 = coerce c3