Reputation: 555
How would I use pattern matching in order to exclude certain types of inputs? For instance given the following:
f list k =
if null list
then []
else head list + k : f (tail list) k
How can I use pattern matching to make f s/t it only allows Int and Integer but not Double or Float?
Upvotes: 2
Views: 784
Reputation:
f
/mapAdd
implements a map
. map
is
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x : xs) = f x : map f xs
and can be used to formulate mapAdd
:
Prelude> mapAdd n = map (+ n)
Prelude> mapAdd 3 [1,2,3,4]
[4,5,6,7]
GHCi's :type
command tells you the automatically inferred type signature of mapAdd
:
Prelude> :type mapAdd
mapAdd :: Num b => b -> [b] -> [b]
Here, mapAdd
's b
s are constrained to the general numerical type class Num
. You can explicitly constrain to the type class Integral
, which (basically) exclusively contains the data types Int
, and Integer
. Integral
is a subclass of 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)
is pointfree for mapAdd n lst = map (+ n) lst
.
Upvotes: 3
Reputation: 475
There are a lot of ways to constrain types, the most direct is just with type families.
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]
This allows you to white list a set of types. You won't know which one in particular you have though, that's more complicated.
However, you said you wanted to exclude certain types, so perhaps you want a black list. We simply flip the first and last case:
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
will now accept any Num
type that is not a Double or Float.
However, there's no reason to actually do any of this for your example function. You lose nothing at all by allowing it to work on the full range of Num
types. In fact, these hijinks will at the least cost you a tiny smidgen more time on type checking, or if you go further and do reflection based things, possibly run time overhead as well.
Upvotes: 0
Reputation: 48611
As suchtgott explained, you should not actually try to limit your function to exactly Int
and Integer
; you probably want to use an Integral
constraint.
Suppose, for fun, that you really did want to limit it just like that. The way to do this in Haskell 98 is a bit weird (jump down to the break to see how this is done in 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
How do you use such a thing? Well, to start off with
intOrIntegerSimple :: IntOrInteger a => Either (a -> Int) (a -> Integer)
intOrIntegerSimple = case intOrInteger of
Left f -> Left (runIdentity . f . Identity)
Right f -> Right (runIdentity . f . Identity)
But how do you flip it around, and turn an Int
or Integer
back to an instance of 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)
When you don't really care whether you have an Int
or an Integer
, but want to be sure you really have one of them, you have to watch out for invalid instances. What might an invalid instance look like? This is one option:
instance IntOrInteger Word where
intOrInteger = Left (const undefined)
To just test that the instance is valid, you can use this (importing Data.Proxy
):
ensureIntOrInteger :: IntOrInteger a => Proxy a -> ()
ensureIntOrInteger p = case intOrInteger of
Left f -> f p `seq` ()
Right f -> f p `seq` ()
The result of ensureIntOrInteger
will be defined if and only if the type a
is actually an Int
or an Integer
.
Nice; it works. But in practice it's pretty nasty to use. You can do much better with a few GHC extensions:
{-# 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
With this formulation, the IntOrIntegerC
constraint family blocks out any invalid types without your needing to do anything, giving a useful error message if someone tries to write a bogus instance. And if you actually do need to use the equality evidence, it's simply a matter of pattern matching on intOrInteger
or using the various handy functions in Data.Type.Equality
.
A point of style: using head
and tail
is generally discouraged in Haskell. We prefer to pattern match instead. Your function could be written
f [] _ = []
f (x : xs) k = x + k : f xs k
Upvotes: 3
Reputation: 39556
You can create a dumb type class with instances only for Int
and Integer
and then use a type constraint:
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
Then if you use f
with Int
, it will compile:
> f [1,2,3] 1
[2,3,4]
But it will not compile with 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)
Upvotes: 1