我可以为某些类型提供可能不需要的多态函数参数吗?

时间:2015-08-07 06:49:20

标签: haskell

我的数据类型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值可以使用而不是错误来正确表达编译器应该能够证明代码永远不会被使用。

2 个答案:

答案 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。对于此特定问题,最好从TrueFalse以及某些类型b() -> bVoid -> 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