views:

198

answers:

4

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?

+16  A: 

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.

Dan
+3  A: 

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"
Michael Steele
+6  A: 

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.

yairchu
That seems... Unnecessarily complicated.
Rayne
@Rayne: The upside is, that if his structure evolved to something complex, and had the `String`s that he wanted to change nested deep inside, this would still stay the same while other approaches would become much longer and boiler-plate-y.
yairchu
+4  A: 

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"
Tim Robinson