Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to combine case statement patterns [duplicate]

Tags:

haskell

ghc

I'm trying to match on many different constructors in a case statement. For simplicity, assume in half the cases we do the same thing, and in the other half we do something else. Even if I factor out the logic to another function, I still have to write:

case x of
  C1 -> foo x
  C2 -> foo x
  ...
  C10 -> bar x
  C11 -> bar x
  ...

Is there some way to make case statements behave more like switch statements in C (i.e. with fallthrough), or so that I can match on one of many patterns at once, like:

case x of
  C1, C2, C3 -> foo x
  C10, C11, C12 -> bar x

Or perhaps another way to clean this up?

like image 537
crockeea Avatar asked Mar 25 '15 01:03

crockeea


People also ask

How do you handle multiple cases in a switch case?

Multiple cases can be combined to execute the same statements. Each case must exit the case explicitly by using break , return , goto statement, or some other way, making sure the program control exits a case and cannot fall through to the default case. The following use the return keyword.

Can Switch case have multiple conditions?

The JavaScript switch case is a multiple if else statement. It takes a conditional expression just like an if statement but can have many conditions—or cases—that can match the result of the expression to run different blocks of code.


1 Answers

These are called disjunctive patterns, and Haskell does not have them. (OCaml and F# do.) There are a few typical workarounds, however. If your type is an enumeration, you can use equality, with for example elem, using a case expression, guards, or MultiWayIf:

exampleCase cond = case cond of
  c
    | c `elem` [C1, C2, C3] -> foo
    | c `elem` [C10, C11, C12] -> bar
    | otherwise -> baz
exampleGuards c
  | c `elem` [C1, C2, C3] -> foo
  | c `elem` [C10, C11, C12] -> bar
  | otherwise -> baz
exampleIf c
  = additionalProcessing $ if
    | c `elem` [C1, C2, C3] -> foo
    | c `elem` [C10, C11, C12] -> bar
    | otherwise -> baz

And of course, if foo or bar are long expressions, thanks to laziness you can simply factor them into local definitions, so you only have to repeat the name and any pattern variables you need as arguments:

exampleWhere cond = case cond of
  C1 x -> foo x
  C2 y -> foo y
  …
  C10 -> bar
  C11 -> bar
  …
  where
    foo x = something long (involving x, presumably)
    bar = if you please then something else quite long

If you frequently group constructors together in this way, you can use the PatternSynonyms language option, which is especially useful in conjunction with ViewPatterns, to make your own patterns for matching such groups:

{-# Language
    LambdaCase,
    PatternSynonyms,
    ViewPatterns #-}

-- Write one function to match each property.

fooish :: T -> Maybe X
fooish = \ case
  C1 x -> Just x
  C2 x -> Just x
  …
  C10 -> Nothing
  C11 -> Nothing
  …
  -- May use a wildcard ‘_’ here; I prefer not to,
  -- to require updating cases when a type changes.

barrish :: T -> Bool
barrish = \ case
  C1{} -> False
  C2{} -> False
  …
  C10 -> True
  C11 -> True
  …

-- Create synonyms for matching those properties.
-- (These happen to be unidirectional only.)

pattern Fooish :: T -> Foo
pattern Fooish x <- (fooish -> Just x)

pattern Barrish :: T -> Bar
pattern Barrish <- (barrish -> True)

-- If they cover all cases, tell the compiler so.
-- This helps produce useful warnings with ‘-Wall’.

{-# Complete Fooish, Barrish #-}

-- Use them just like normal patterns.

exampleSynonyms x = case x of
  Fooish x -> …
  …
  Barrish -> …
  …
like image 182
Jon Purdy Avatar answered Oct 10 '22 17:10

Jon Purdy