Difference between revisions of "Case"
m |
(add MultiWayIf) |
||
(7 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
+ | == Question == |
||
+ | |||
Can I have a <hask>case</hask> where the alternatives contain expressions? |
Can I have a <hask>case</hask> where the alternatives contain expressions? |
||
+ | == Answer == |
||
+ | There are several approaches to this problem. |
||
⚫ | |||
+ | === Using functions === |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
+ | ==== select ==== |
||
⚫ | |||
− | + | We can do this nicely with a function implemented in Haskell: |
|
<haskell> |
<haskell> |
||
select :: a -> [(Bool, a)] -> a |
select :: a -> [(Bool, a)] -> a |
||
select def = maybe def snd . List.find fst |
select def = maybe def snd . List.find fst |
||
⚫ | |||
− | |||
+ | -- = maybe def id . lookup True |
||
select exDefault |
select exDefault |
||
Line 25: | Line 23: | ||
(cond3, ex3)] |
(cond3, ex3)] |
||
</haskell> |
</haskell> |
||
+ | Unfortunately this function is not in the [[Prelude]]. |
||
+ | It is however in the [http://hackage.haskell.org/packages/archive/utility-ht/0.0.1/doc/html/Data-Bool-HT.html#v%3Aselect utility-ht] package. |
||
+ | |||
+ | ==== nested 'if' ==== |
||
Alternative implementations are |
Alternative implementations are |
||
<haskell> |
<haskell> |
||
⚫ | |||
− | |||
{- a purely functional implementation of if-then-else -} |
{- a purely functional implementation of if-then-else -} |
||
if' :: Bool -> a -> a -> a |
if' :: Bool -> a -> a -> a |
||
Line 39: | Line 39: | ||
The implementation of <hask>select''</hask> makes clear that <hask>select</hask> can be considered as nested <hask>if</hask>s. |
The implementation of <hask>select''</hask> makes clear that <hask>select</hask> can be considered as nested <hask>if</hask>s. |
||
The functional <hask>if'</hask> is also useful in connection with <hask>zipWith3</hask> since <hask>zipWith3 if'</hask> merges two lists according to a list of conditions. |
The functional <hask>if'</hask> is also useful in connection with <hask>zipWith3</hask> since <hask>zipWith3 if'</hask> merges two lists according to a list of conditions. |
||
+ | See [[if-then-else]]. |
||
+ | Alternatively you can unroll <hask>foldr</hask> and write |
||
− | |||
− | If you don't like the parentheses for the pairs, you can also define |
||
<haskell> |
<haskell> |
||
+ | if' cond1 ex1 $ |
||
− | data SelectBranch a = (:->) { |
||
⚫ | |||
− | condition :: Bool, |
||
⚫ | |||
− | expression :: a |
||
⚫ | |||
− | } |
||
− | |||
− | select :: a -> [SelectBranch a] -> a |
||
− | select def = maybe def expression . List.find condition |
||
− | |||
− | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
</haskell> |
</haskell> |
||
+ | ==== infix operator ==== |
||
− | It is also possible to define a ternary operator '?' like in C. |
||
⚫ | |||
− | '$' for the else clause. |
||
+ | If you use <hask>if'</hask> in infix form, |
||
+ | you may call it <hask>?</hask> like in C, |
||
⚫ | |||
<haskell> |
<haskell> |
||
infixl 1 ? |
infixl 1 ? |
||
(?) :: Bool -> a -> a -> a |
(?) :: Bool -> a -> a -> a |
||
− | + | (?) = if' |
|
− | False ? _ = id |
||
cond1 ? ex1 $ |
cond1 ? ex1 $ |
||
cond2 ? ex2 $ |
cond2 ? ex2 $ |
||
− | cond3 ? ex3 $ |
+ | cond3 ? ex3 $ |
+ | exDefault |
||
⚫ | |||
+ | |||
+ | === Using syntactic sugar === |
||
+ | |||
+ | ==== Guards ==== |
||
+ | |||
⚫ | |||
+ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
+ | </haskell> |
||
+ | |||
⚫ | |||
+ | |||
+ | ==== List comprehensions ==== |
||
+ | |||
+ | An alternative sugarful approach is to use [[list comprehension]]s. |
||
+ | |||
+ | <haskell> |
||
+ | head $ |
||
⚫ | |||
+ | [ ex2 | cond2 ] ++ |
||
+ | [ ex3 | cond3 ] ++ |
||
+ | [ exDefault ] |
||
+ | </haskell> |
||
+ | |||
+ | === MultiWayIf === |
||
+ | |||
+ | The MultiWayIf extension lets you write code similar to a <hask>case () of _</hask> form, using only the word <hask>if</hask>. To enable it, add <hask>{-# LANGUAGE MultiWayIf #-}</hask> to the top of a <tt>.hs</tt> file, run ghci with <tt>ghci -XMultiWayIf</tt>, or add <tt>MultiWayIf</tt> to the <tt>default-extensions</tt> in your <tt>.cabal</tt> file. |
||
+ | |||
+ | <haskell> |
||
+ | if | guard1 -> expr1 |
||
+ | | ... |
||
+ | | guardN -> exprN |
||
</haskell> |
</haskell> |
||
+ | [[Category:FAQ]] |
||
[[Category:Idioms]] |
[[Category:Idioms]] |
Latest revision as of 11:36, 11 June 2020
Question
Can I have a case
where the alternatives contain expressions?
Answer
There are several approaches to this problem.
Using functions
select
We can do this nicely with a function implemented in Haskell:
select :: a -> [(Bool, a)] -> a
select def = maybe def snd . List.find fst
-- = fromMaybe def . lookup True
-- = maybe def id . lookup True
select exDefault
[(cond1, ex1),
(cond2, ex2),
(cond3, ex3)]
Unfortunately this function is not in the Prelude. It is however in the utility-ht package.
nested 'if'
Alternative implementations are
{- a purely functional implementation of if-then-else -}
if' :: Bool -> a -> a -> a
if' True x _ = x
if' False _ y = y
select'' = foldr (uncurry if')
The implementation of select''
makes clear that select
can be considered as nested if
s.
The functional if'
is also useful in connection with zipWith3
since zipWith3 if'
merges two lists according to a list of conditions.
See if-then-else.
Alternatively you can unroll foldr
and write
if' cond1 ex1 $
if' cond2 ex2 $
if' cond3 ex3 $
exDefault
infix operator
If you use if'
in infix form,
you may call it ?
like in C,
then because of partial application it will work nicely together with '$' for the else clause.
infixl 1 ?
(?) :: Bool -> a -> a -> a
(?) = if'
cond1 ? ex1 $
cond2 ? ex2 $
cond3 ? ex3 $
exDefault
Using syntactic sugar
Guards
You can make use of some syntactic sugar of Haskell, namely of guards.
case () of _
| cond1 -> ex1
| cond2 -> ex2
| cond3 -> ex3
| otherwise -> exDefault
Alternatively, one could simply factor out a function(/value) and use guards in the argument patterns.
List comprehensions
An alternative sugarful approach is to use list comprehensions.
head $
[ ex1 | cond1 ] ++
[ ex2 | cond2 ] ++
[ ex3 | cond3 ] ++
[ exDefault ]
MultiWayIf
The MultiWayIf extension lets you write code similar to a case () of _
form, using only the word if
. To enable it, add {-# LANGUAGE MultiWayIf #-}
to the top of a .hs file, run ghci with ghci -XMultiWayIf, or add MultiWayIf to the default-extensions in your .cabal file.
if | guard1 -> expr1
| ...
| guardN -> exprN