Is it possible to make a generic function that would take either Foo or Bar as an argument and would return a function that uses that argument in its pattern matching?
For instance, if I have
isFoo :: SomeData -> Bool
isFoo (Foo _) = True
isFoo _ = False
isBar :: SomeData -> Bool
isBar (Bar _) = True
isBar _ = False
Is there a way to create a generic function, something like
checkType :: SomeClass -> SomeData -> Bool
checkType (SomeClass _) = True
checkType _ = False
I realize the situation looks a little odd, and the actual use case is a little more complex, but the problem is identical.
The actual code I'm trying to refactor is the following
isString :: [LispVal] -> ThrowsError LispVal
isString [(String _)] = return $ Bool True
isString ((String _):xs) = isString xs >>= unpackBool >>= return . Bool
isString _ = return $ Bool False
isSymbol :: [LispVal] -> ThrowsError LispVal
isSymbol [(Atom _)] = return $ Bool True
isSymbol ((Atom _):xs) = isSymbol xs >>= unpackBool >>= return . Bool
isSymbol _ = return $ Bool False
isNumber :: [LispVal] -> ThrowsError LispVal
isNumber [(Number _)] = return $ Bool True
isNumber ((Number _):xs) = isNumber xs >>= unpackBool >>= return . Bool
isNumber _ = return $ Bool False
So I'd like some way to make this more dry
Prism
s from the lens
library can act as "first-class patterns". To define prisms for your datatype:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data SomeData = Foo Int
| Bar Char
-- Will create prisms named _Foo and _Bar
$(makePrisms ''SomeData)
Since Prism
s are valid Fold
s, we can pass them to the has
function from Control.Lens.Fold
:
*Main> has _Foo (Foo 5)
True
*Main> has _Bar (Foo 5)
False
Another interesting application of prisms as first-class patterns is "overriding" the behaviour of a function for cases in which the argument matches the prism. You can use outside
from Control.Lens.Prism
to do that. outside
is a function that takes a Prism
and returns a Lens
for functions, that allows you to "set" the special case. For example:
functionToOverride :: SomeData -> Int
functionToOverride = const 5
-- If the arg is a Foo, return the contained int + 1
newFunction :: SomeData -> Int
newFunction = functionToOverride & outside _Foo .~ succ
Testing both functions:
*Main> functionToOverride (Foo 77)
5
*Main> newFunction (Bar 'a')
5
*Main> newFunction (Foo 77)
78
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
import Data.Typeable
data Foo a = Foo1 a | Foo2 a deriving (Data, Typeable)
data Bar a = Bar1 a | Bar2 a deriving (Data, Typeable)
checkType :: Data a => Constr -> a -> Bool
checkType c v = c == toConstr v
example
zeroFoo1 = Foo1 (0 :: Int)
isFoo1 = checkType (toConstr zeroFoo1)
to generalize over a
your checkType
, you need a constant value (e.g. mempty
) for each constructor.
(really, the only trick is toConstr a == toConstr b
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With