问题
Assume I have a character c :: Char
. Now I want to see if it's equal to a
or isDigit
:
isAOrDigit = (||) <$> (=='a') <*> (isDigit)
So far so good. But now I want to see if it's equal to a
, isDigit
or is between d
and g
. Unfortunately, since ||
only accepts 2 arguments, I can't say (||) <$> (=='a') <*> (isDigit) <*> (`elem`['d'..'g'])
.
Is there any nice way to write this or do i have to fall back to this:
isACOrDigitOrDG c = c == 'a' || isDigit c || c `elem` ['d'..'g']
?
回答1:
You can use sequence
to convert a [a -> Bool]
to a a -> [Bool]
. Then you can use or
to combine that [Bool]
value.
isACOrDigitOrDG = or . sequence
[ (== 'a')
, isDigit
, (`elem` ['d'..'g'])
]
回答2:
You can lift the (||)
operator:
(<||>) = liftA2 (||)
> :t (== 'a') <||> isDigit <||> (`elem` ['d'..'g'])
(== 'a') <||> isDigit <||> (`elem` ['d'..'g']) :: Char -> Bool
> (== 'a') <||> isDigit <||> (`elem` ['d'..'g']) $ 'a'
True
回答3:
Depending on your definition of "nice", you could write
isACOrDigitOrDG :: Char -> Bool
isACOrDigitOrDG = anyOf [(== 'a'), isDigit, (`elem` ['d'..'g'])]
where anyOf fs c = or (fs <*> [c])
-- anyOf = (or .) . (. pure) . (<*>)
or :: [Bool] -> Bool
is true if any value in its input is true. The applicative instance for lists says that fs <*> xs
applies each function in fs
to each value in xs
, and collects the results in a single list (for our purposes here, the order doesn't matter). Since we only have one Char
, we create a list so that fs <*> [c]
applies each predicate to the input character. or
then tells us if any of those applications succeeded.
Honestly, though? For a fixed set of predicates, there's nothing wrong with
isACOrDigitOrDG c = c == 'a' || isDigit c || c `elem` ['d'..'g']
Point-free style can be hit-or-miss; sometimes it captures the idea of what you want to do better than a pointed defintion, other times it's just terse for the sake of terseness. I often write a function in both styles before deciding which one I'll want to read later.
As a third option between the two, you might take advantage of a list comprehension:
isACOrDigitOrDG c = or [f c | f <- tests]
where tests = [(== 'a')
,isDigit
,(`elem` ['d'..'g'])
]
回答4:
Another version that is similar to the other answers and short circuiting:
isACOrDigitOrDG x = any ($x) [(=='a'), isDigit, (`elem` ['d'..'g'])]
or if you'd rather have the slightly ridiculous pointfree version:
isACOrDigitOrDG = flip any [(=='a'), isDigit, (`elem` ['d'..'g'])] . flip ($)
or even better, just give that a name and use it:
satisfies :: [a -> Bool] -> a -> Bool
satisfies ps x = any ($x) ps
isACOrDigitOrDG = satisfies [(=='a'), isDigit, (`elem` ['d'..'g'])]
来源:https://stackoverflow.com/questions/55854726/how-to-nicely-denote-the-alternative-of-three-conditions