如何使用模式匹配来排除某些类型的输入?例如,给出以下内容:
f list k =
if null list
then []
else head list + k : f (tail list) k
如何使用模式匹配来制作f s / t它只允许Int和Integer但不允许使用Double或Float?
答案 0 :(得分:3)
f
/ mapAdd
实现map
。 map
是
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
,它(基本上)只包含数据类型Num
和Int
。 Integer
是Integral
的子类。
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所解释的那样,您实际上不应该尝试将您的功能限制为Int
和Integer
;你可能想要使用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)
但是你如何翻转它,然后将Int
或Integer
转回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)
如果您真的不关心自己是否有Int
或Integer
,但想确定自己确实拥有其中一个,则必须注意无效的实例。无效实例可能是什么样的?这是一个选择:
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
实际上是a
或Int
时,才会定义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中通常不鼓励使用head
和tail
。我们更喜欢模式匹配。你的功能可以写成
f [] _ = []
f (x : xs) k = x + k : f xs k
答案 2 :(得分:1)
您可以创建一个仅包含Int
和Integer
实例的哑类型类,然后使用类型约束:
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
然后,如果您将f
与Int
一起使用,则会编译:
> 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至少会让你在类型检查上花费更少的时间,或者如果你更进一步做基于反射的事情,也可能会花费时间开销。