排除Haskell中的类型

时间:2017-04-15 00:37:00

标签: haskell

如何使用模式匹配来排除某些类型的输入?例如,给出以下内容:

f list k =
    if null list
    then []
    else head list + k : f (tail list) k

如何使用模式匹配来制作f s / t它只允许Int和Integer但不允许使用Double或Float?

4 个答案:

答案 0 :(得分:3)

f / mapAdd实现mapmap

map :: (a -> b) -> [a] -> [b]
map _ []       = []
map f (x : xs) = f x : map f xs

可用于制定mapAdd

Prelude> mapAdd n  =  map (+ n)
Prelude> mapAdd 3 [1,2,3,4]
[4,5,6,7]

GHCi的:type命令告诉您mapAdd的自动推断类型签名:

Prelude> :type mapAdd
mapAdd :: Num b => b -> [b] -> [b]

此处,mapAdd的{​​{1}}被约束为一般数字类型 b。您可以明确约束类型类Integral,它(基本上)只包含数据类型NumIntIntegerIntegral子类

Num
对于Prelude> :{ Prelude| mapAdd :: Integral a => a -> [a] -> [a] Prelude| mapAdd n = map (+ n) Prelude| :} Prelude> :t mapAdd mapAdd :: Integral a => a -> [a] -> [a]

mapAdd n = map (+ n) pointfree

答案 1 :(得分:3)

正如thistgott所解释的那样,您实际上不应该尝试将您的功能限制为IntInteger;你可能想要使用Integral约束。

假设,为了好玩,你确实想要限制它。在Haskell 98中执行此操作的方法有点奇怪(跳转到中断,看看如何在GHC Haskell中完成此操作:

class Integral a => IntOrInteger a where
  intOrInteger :: Either (f a -> f Int) (f a -> f Integer)
instance IntOrInteger Int where
  intOrInteger = Left id
instance IntOrInteger Integer where
  intOrInteger = Right id

你怎么用这样的东西?好吧,从

开始
intOrIntegerSimple :: IntOrInteger a => Either (a -> Int) (a -> Integer)
intOrIntegerSimple = case intOrInteger of
  Left f -> Left (runIdentity . f . Identity)
  Right f -> Right (runIdentity . f . Identity)

但是你如何翻转它,然后将IntInteger转回IntOrInteger的实例?

newtype Switch f a i = Switch {runSwitch :: f i -> f a}

intOrInteger' :: IntOrInteger a => Either (f Int -> f a) (f Integer -> f a)
intOrInteger' = case intOrInteger of
                  Left f -> Left (runSwitch (f (Switch id)))
                  Right f -> Right (runSwitch (f (Switch id)))

intOrIntegerSimple' :: IntOrInteger a => Either (Int -> a) (Integer -> a)
intOrIntegerSimple' = case intOrInteger' of
  Left f -> Left (runIdentity . f . Identity)
  Right f -> Right (runIdentity . f . Identity)

如果您真的不关心自己是否有IntInteger,但想确定自己确实拥有其中一个,则必须注意无效的实例。无效实例可能是什么样的?这是一个选择:

instance IntOrInteger Word where
  intOrInteger = Left (const undefined)

要测试该实例是否有效,您可以使用它(导入Data.Proxy):

ensureIntOrInteger :: IntOrInteger a => Proxy a -> ()
ensureIntOrInteger p = case intOrInteger of
  Left f -> f p `seq` ()
  Right f -> f p `seq` ()

当且仅当类型ensureIntOrInteger实际上是aInt时,才会定义Integer的结果。

尼斯;有用。但在实践中使用起来非常讨厌。使用一些GHC扩展可以做得更好:

{-# LANGUAGE GADTs, TypeFamilies, TypeOperators, ConstraintKinds,
      UndecidableInstances, UndecidableSuperClasses, DataKinds #-}

-- In 8.0 and later, Constraint is also available from Data.Kind
import GHC.Exts (Constraint)
import Data.Type.Equality ((:~:)(..))
import GHC.TypeLits (TypeError, ErrorMessage (..))

type family IntOrIntegerC a :: Constraint where
  IntOrIntegerC Int = ()
  IntOrIntegerC Integer = ()
  IntOrIntegerC t = TypeError ('ShowType t :<>:
                               'Text " is not an Int or an Integer.")

class (Integral a, IntOrIntegerC a) => IntOrInteger a where
  intOrInteger :: Either (a :~: Int) (a :~: Integer)
instance IntOrInteger Int where
  intOrInteger = Left Refl
instance IntOrInteger Integer where
  intOrInteger = Right Refl

使用此公式,IntOrIntegerC约束族可以阻止任何无效类型而无需执行任何,如果有人试图编写虚假实例,则会提供有用的错误消息。如果您确实需要使用相等证据,那么只需要intOrInteger上的模式匹配或使用Data.Type.Equality中的各种便捷函数。

风格点:在Haskell中通常不鼓励使用headtail。我们更喜欢模式匹配。你的功能可以写成

f [] _ = []
f (x : xs) k = x + k : f xs k

答案 2 :(得分:1)

您可以创建一个仅包含IntInteger实例的哑类型类,然后使用类型约束:

class (Num a) => IntOrInteger a
instance IntOrInteger Int
instance IntOrInteger Integer

f :: (IntOrInteger a) => [a] -> a -> [a]
f list k =
    if null list
    then []
    else head list + k : f (tail list) k

然后,如果您将fInt一起使用,则会编译:

> f [1,2,3] 1
[2,3,4]

但它不能用Double编译:

> f [1,2,3] (1::Double)

<interactive>:19:1: error:
    * No instance for (IntOrInteger Double) arising from a use of `f'
    * In the expression: f [1, 2, 3] (1 :: Double)
      In an equation for `it': it = f [1, 2, 3] (1 :: Double)

答案 3 :(得分:0)

有很多方法可以约束类型,最直接的方法就是使用类型族。

type family Elem x xs :: Constraint where
    Elem x (x ': xs) = ()
    Elem x (y ': xs) = Elem x xs
    Elem x '[] = TypeError ('ShowType x :<>: 'Text " is not a permitted type.")

type a ~~ b = Elem a b

function :: (a ~~ [Int, Integer], Num a) => [a] -> [a]

这允许您列出一组类型的白名单。你不会特别知道你有哪一个,这更复杂。

但是,你说你想排除某些类型,所以也许你想要一个黑名单。我们只是翻转第一个和最后一个案例:

type family NotElem x xs :: Constraint where
    NotElem x (x ': xs) = TypeError ('ShowType x :<>: 'Text " is a forbidden type.")
    NotElem x (y ': xs) = NotElem x xs
    NotElem x '[] = ()

type a !~~ b = NotElem a b

function :: (a !~~ [Double, Float], Num a) => [a] -> [a]

function现在将接受 Double或Float的任何Num类型。

但是,没有理由为您的示例函数实际执行此操作。通过允许它在全部Num类型上工作,您什么都不会丢失。事实上,这些hijinks至少会让你在类型检查上花费更少的时间,或者如果你更进一步做基于反射的事情,也可能会花费时间开销。