可以使用不同的参数集捕获和重用构造函数



如果我们有一个类型"人"定义如下:

--datatype in record syntax
data Person = Male { firstName :: String, lastName :: String } | 
              Female { firstName :: String, lastName :: String }

这个可以:

flipNames :: Person -> Person
flipNames p@(Male{}) = Male (lastName p) (firstName p)
flipNames p@(Female{}) = Female (lastName p) (firstName p)

写成翻转名称的一个定义?我们能否以某种方式捕获使用的构造函数,并将其与不同的参数重用?像这样:

flipNames (constructor fname lname) = c lname fname

虽然Ganesh已经回答了你的确切问题,但我想说你的问题只是表明数据类型设计方法不正确。

以下方法更加灵活,可以消除您的问题:

data Person = Person { gender :: Gender, firstName :: String, lastName :: String }
data Gender = Male | Female
flipNames (Person gender firstName lastName) = Person gender lastName firstName

这背后的规则非常简单:每当您看到自己使用相同的字段创建多个构造函数时,只需使用单个构造函数并引入另一个具有枚举类型的字段,如上面的代码所示。

您不会失去任何模式匹配功能,因为模式可以像Person Male firstName lastName一样,并且您将能够使Gender类型派生EnumBounded这肯定会帮助您处理不那么简单的类型。 例如:

data Gender = Male | Female deriving (Enum, Bounded)
allGenders :: [Gender]
allGenders = enumFrom minBound
maidenName :: Person -> Maybe String
maidenName (Person Female _ z) = Just z
maidenName _ = Nothing

在这种特殊情况下,您可以这样做:

flipNames :: Person -> Person
flipNames p = p { firstName = lastName p , lastName = firstName p }

但是,这仅适用于MaleFemale的记录选择器相同。没有通用的抽象可以在没有参数的情况下捕获构造函数。

若要添加另一个选项,可以对虚拟类型执行类似的操作。 请注意,您要这样做是因为您的Person构造函数是多余的,它们的存在只是为了区分男性和女性。 您可以将这种区别提升到类型系统中,让类型推断处理Male/Female部分。

{-# LANGUAGE FlexibleInstances #-}
data Person a = Person { first :: String, last :: String }
  deriving (Show, Eq)
data Male
data Female
flipName :: Person a -> Person a
flipName (Person f l) = Person l f
main = do
  let m = Person "John" "Doe" :: Person Male
      f = Person "Jane" "Doe" :: Person Female
  print m
  print f
  print (flipName m)
  print (flipName f)
  print (gender f)
  print (gender m)
class Gender a where
  gender :: a -> String
instance Gender (Person Male) where
  gender _ = "Male"
instance Gender (Person Female) where
  gender _ = "Female"

在文件person.hs中使用它,您将获得以下输出:

╰─➤  runhaskell person.hs
Person {first = "John", last = "Doe"}
Person {first = "Jane", last = "Doe"}
Person {first = "Doe", last = "John"}
Person {first = "Doe", last = "Jane"}
"Female"
"Male"

这样做的缺点是您可能不想随身携带额外的类型参数。 但是,好处是,您现在可以根据MaleFemale类型使用不同的实现来定义类型类实例。 尽管要执行后者需要FlexibleInstances扩展。

是的,您可以使用视图模式执行类似(但不完全相同)的操作!

{-# LANGUAGE ViewPatterns #-}
data Person = Male     { firstName :: String, lastName :: String }  
            | Female   { firstName :: String, lastName :: String }
            | NewBorn  { birthdate :: String }
            | Child    { firstName :: String, lastName :: String }
            | Teenager { firstName :: String, lastName :: String }

isAdult :: Person -> Bool
isAdult (Male {})   = True
isAdult (Female {}) = True
isAdult _           = False
flipNames :: Person -> Person
flipNames p@(isAdult -> True)  = p{firstName=lastName p, lastName=firstName p}
flipNames p@(isAdult -> False) = p

你不能将变量与这样的构造函数匹配,因为模式在 Haskell 中不是一等公民。你可以拥有特定于函数的代码,但不能使用通用代码。

如果你对这样的想法感兴趣,看看研究语言bondi,它确实支持这样的匹配构造函数。它实际上开辟了一个有趣的表现力新脉络。在实践中,它使得在代数数据类型的确切结构上编写通用代码变得更加容易。

最新更新