Type Matching in Haskell
-
18-09-2019 - |
Question
If SomeType is defined as:
data SomeType = X {myBool :: Bool}
| Y {myString :: String}
| Z {myString :: String}
and I will update an arbitrary X, dependent of his type as follows:
changeST :: SomeType -> SomeType
changeST (X b) = (X True)
changeST (Y s) = (Y "newString")
changeST (Z s) = (Z "newString")
The third and the fourth line do the very same, they update the string in the given type. Is there any way replace these two lines by a single one, eg. by assigning the type to a variable?
Solution
Not by assigning the type to a variable, but by doing field replacement:
changeST :: SomeType -> SomeType
changeST (X b) = (X True)
changeST st = st { myString = "newString" }
This returns the same st as its argument, but with the value of the myString
field replaced. It's one of the nice features of fields that you can do this without caring which data constructor it is, as long as it's one of the data constructors that uses myString
.
OTHER TIPS
You can use Scrap-Your-Boilerplate for this.
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Generics
data SomeType
= X { myBool :: Bool }
| Y { myString :: String }
| Z { myString :: String }
deriving (Data, Typeable)
changeST :: SomeType -> SomeType
changeST = everywhere (mkT (const True)) . everywhere (mkT (const "newString"))
This changeST
changes every internal String
in your structure to "newString"
and every Bool
to True
.
I prefer Dan's solution, but pattern guards in GHC (standard in Haskell 2010) are a neat alternative to Michael's proposal:
{-# LANGUAGE PatternGuards #-}
changeST :: SomeType -> SomeType
changeST x | X _ <- x = X True
| Y _ <- x = Y newString
| Z _ <- x = Z newString
where newString = "newString"
Your three definitions of changeST
are separate from each other, so the short answer is "no". There are, however, at least two ways you can do this.
Pattern match both the Y
and Z
constructors at once:
You can combine the 2nd and 3rd definition by making your pattern matching more general:
changeST x = x { myString = "newString"}
This creates a new version of x
, whether it be a Y
or a Z
, replacing the string member. You have to be careful when doing this, though. If you later rename the string field of Z
, for example, you will get runtime pattern match failures when calling changeST with a Z
argument.
Use a case expression:
If you combine your three definitions into one, you can share data between them.
changeST :: SomeType -> SomeType
changeST x = case x of
X _ -> X True
Y _ -> Y newString
Z _ -> Z newString
where
newString = "newString"