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

时间:2014-01-16 22:04:02

标签: haskell constructor programming-languages pattern-matching

如果我们的类型定义如下:

--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 的一个定义?我们能以某种方式捕获使用的构造函数,并将其重用于不同的参数吗? 类似的东西:

flipNames (constructor fname lname) = c lname fname

5 个答案:

答案 0 :(得分:11)

虽然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肯定会帮助你解决那些并非琐碎的类型。 E.g:

data Gender = Male | Female deriving (Enum, Bounded)

allGenders :: [Gender]
allGenders = enumFrom minBound

maidenName :: Person -> Maybe String
maidenName (Person Female _ z) = Just z
maidenName _ = Nothing

答案 1 :(得分:5)

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

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

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

答案 2 :(得分:3)

要添加其他选项,您可以使用幻像类型执行类似操作。请注意,您希望这样做是因为您的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扩展名。

答案 3 :(得分:2)

是的,你可以使用视图模式做类似的(但不完全相同)!

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

答案 4 :(得分:1)

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

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