4

如果 SomeType 定义为:

data SomeType = X {myBool :: Bool} 
                | Y {myString :: String} 
                | Z {myString :: String}

我将根据他的类型更新任意 X,如下所示:

changeST :: SomeType -> SomeType
changeST (X b) = (X True)
changeST (Y s) = (Y "newString")
changeST (Z s) = (Z "newString")

第三和第四行的作用相同,它们更新给定类型的字符串。有没有办法用一条替换这两行,例如。通过将类型分配给变量?

4

4 回答 4

17

不是通过将类型分配给变量,而是通过进行字段替换:

changeST :: SomeType -> SomeType
changeST (X b) = (X True)
changeST st = st { myString = "newString" }

这将返回与其参数相同的 st,但myString替换了字段的值。这是字段的一个不错的特性之一,您可以在不关心它是哪个数据构造函数的情况下执行此操作,只要它是使用myString.

于 2009-12-09T16:10:22.310 回答
7

您可以为此使用Scrap-Your-Boilerplate

{-# 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"))

这会将结构中的changeST每个内部更改String"newString"和每个BoolTrue

于 2009-12-09T17:10:02.553 回答
4

我更喜欢 Dan 的解决方案,但 GHC 中的模式保护(Haskell 2010 中的标准)是 Michael 提议的一个很好的替代方案:

{-# LANGUAGE PatternGuards #-}
changeST :: SomeType -> SomeType
changeST x | X _ <- x = X True
           | Y _ <- x = Y newString
           | Z _ <- x = Z newString
  where    newString = "newString"
于 2009-12-09T18:07:18.030 回答
3

你的三个定义是changeST相互独立的,所以简短的回答是“不”。但是,至少有两种方法可以做到这一点。

模式同时匹配 theYZ构造函数:

您可以通过使模式匹配更通用来组合第二和第三定义:

changeST x = x { myString = "newString"}

这将创建一个新版本的x,无论是 aY还是 a Z,替换字符串成员。但是,您在执行此操作时必须小心。例如,如果您稍后重命名 的字符串字段Z,则在使用参数调用 changeST 时会出现运行时模式匹配失败Z

使用 case 表达式:

如果将三个定义合二为一,则可以在它们之间共享数据。

changeST :: SomeType -> SomeType
changeST x = case x of
    X _ -> X True
    Y _ -> Y newString
    Z _ -> Z newString
  where
    newString = "newString"
于 2009-12-09T16:44:15.183 回答