Reputation: 685
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?
Upvotes: 4
Views: 508
Reputation: 11079
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
.
Upvotes: 17
Reputation: 54764
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"
Upvotes: 4
Reputation: 24804
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
.
Upvotes: 7
Reputation: 15772
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.
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.
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"
Upvotes: 3