我的数据类型F
包含Int
的特殊情况:
{-# LANGUAGE GADTs, RankNTypes #-}
data F a where
FGen :: a -> F a
FInt :: F Int
不向调用者公开此数据类型的详细信息 - 真正的数据类型更复杂,包含内部实现细节 - 我想提供一个使用它的API:
transform :: (a -> b) -> b -> F a -> b
transform f i (FGen v) = f v
transform f i FInt = i
如果我打算在transform
上致电F Int
,显然前两个论点都很重要:
transformInt :: F Int -> Int
transformInt = transform (+1) 5
但如果我打算在F Char
上调用它,则第二个参数是不必要的,因为该值不能是FInt
:
transformChar :: F Char -> Char
transformChar = transform id (error "unreachable code")
我可以用transform
的类型表达这个吗?
我试过
transform :: (a -> b) -> (a ~ Int => b) -> F a -> b
transform f i (FGen v) = f v
transform f i FInt = i
但是transformChar
不能用
Couldn't match type ‘Char’ with ‘Int’
Inaccessible code in
a type expected by the context: (Char ~ Int) => Char
In the second argument of ‘transform’, namely
‘(error "unreachable code")’
In the expression: transform id (error "unreachable code")
In an equation for ‘transformChar’:
transformChar = transform id (error "unreachable code")
无论如何我仍然希望absurd
值可以使用而不是错误来正确表达编译器应该能够证明代码永远不会被使用。
答案 0 :(得分:6)
我们可以在Data.Type.Equality
中使用命题相等类型,我们也可以使用空案例表达式来表达来自GHC 7.8的代码的不可访问性:
{-# LANGUAGE GADTs, RankNTypes, EmptyCase, TypeOperators #-}
import Data.Type.Equality
data F a where
FGen :: a -> F a
FInt :: F Int
transform :: (a -> b) -> ((a :~: Int) -> b) -> F a -> b
transform f i (FGen v) = f v
transform f i FInt = i Refl
transformChar :: F Char -> Char
transformChar = transform id (\p -> case p of {})
-- or (\case {}) with LambdaCase
transformInt :: F Int -> Int
transformInt = transform (+1) (const 5)
答案 1 :(得分:4)
我更喜欢GADT for the type equality proof的答案。这个答案解释了如何用TypeFamilies
做同样的事情。对于封闭类型族,我们可以将函数从类型写入类型系统的单位()
和零Void
,以表示介词真值和假。
{-# LANGUAGE TypeFamilies #-}
import Data.Void
type family IsInt a where
IsInt Int = ()
IsInt a = Void
当transform
和() -> b
IsInt a
的类型Void -> b
时,absurd
的第二个参数为a
整数。
transform :: (a -> b) -> (IsInt a -> b) -> F a -> b
transform f i (FGen v) = f v
transform f i FInt = i ()
transformChar
可以用absurd
编写,transformInt
必须作为常量函数传入b
。
transformChar :: F Char -> Char
transformChar = transform id absurd
transformInt :: F Int -> Int
transformInt = transform (+1) (const 5)
在András Kovács建议时,我们可以使用类型系列(==)
更加可重用,以返回提升的Bool
。
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
type family (==) a b :: Bool where
(==) a a = True
(==) a b = False
我们可以提供另一个类型系列来将True
转换为()
和False
转换为Void
。对于此特定问题,最好从True
或False
以及某些类型b
到() -> b
或Void -> b
一直走。
type family When p b where
When True b = () -> b
When False b = Void -> b
然后transform
的类型读取。
transform :: (a -> b) -> When (a == Int) b -> F a -> b