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?

Was it helpful?

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"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top