Haskell:创建一个只包含某些“信息”的列表;“种类”;类型?

Haskell:创建一个只包含某些“信息”的列表;“种类”;类型?,haskell,types,coding-style,Haskell,Types,Coding Style,我一直在努力学习Haskell和开始Haskell,发现了一个有趣的问题。在前言中,我通常是C++程序员,所以如果我不知道我在说什么,请原谅我。 Haskell开头的一个练习让我创建一个类型客户机,它可以是政府组织、公司或个人。我决定为此尝试记录语法 data Client = GovOrg { name :: String } | Company { name :: String, id :: Integer,

我一直在努力学习Haskell和开始Haskell,发现了一个有趣的问题。在前言中,我通常是C++程序员,所以如果我不知道我在说什么,请原谅我。 Haskell开头的一个练习让我创建一个类型客户机,它可以是政府组织、公司或个人。我决定为此尝试记录语法

data Client = GovOrg { name ::  String }
  | Company { name     :: String,
              id       :: Integer,
              contact  :: String,
              position :: String
            }
  | Individual { fullName :: Person,
                 offers   :: Bool
               }
  deriving Show

data Person = Person { firstName :: String,
                       lastName  :: String,
                       gender    :: Gender
                     }
            deriving Show

data Gender = Male | Female | Unknown
            deriving Show
这是用于一个练习,在这个练习中,给定一个客户列表,我必须找出每个性别中有多少人在列表中。我开始筛选,以获得一个只有个人的列表,因为只有他们有性别类型,但我的方法似乎完全错误:

listIndividuals :: [Client] -> [Client]
listIndividuals xs = filter (\x -> x == Individual) xs

我如何获得这个功能,在这里我可以检查什么是“类型”的客户端。同样对于记录语法,我的编码风格如何?太不一致了?

首先,我建议不要将记录类型与代数类型一起使用,因为最终会使用部分访问器函数。例如,让code
位置(个人(personal,“John”“Doe”Person)为True)是完全合法的
,但它会引发运行时错误。相反,考虑一些更像

data GovClient = GovClient {
    govName :: String
    } deriving Show

data CompanyClient = CompanyClient {
    companyName :: String,
    companyID :: Integer,        -- Also, don't overwrite existing names, `id` is built-in function
    companyContact :: String,
    companyPosition :: String
    } deriving Show

data IndividualClient = IndividualClient {
    indvFullName :: Person,
    indvOffers :: Bool
    } deriving Show
那你就可以

data Client
    = GovOrg GovClient
    | Company CompanyClient
    | Individual IndividualClient
    deriving (Show)
现在您还可以将函数定义为

isIndividualClient :: Client -> Bool
isIndividualClient (Individual _) = True
isIndividualClient _ = False

listIndividuals :: [Client] -> [IndividualClient]
listIndividuals clients = filter isIndividualClient clients
或者更无点形式的

listIndividuals = filter isIndividualClient
在这里,为了做出决定,我只是在一个单独的函数中使用模式匹配来确定使用了
客户机的构造函数中的哪一个。现在,您可以使用记录和代数类型的全部功能,只需再担心一点点代码,但安全性要高得多。例如,您永远不会意外地在单个客户机上调用一个需要政府客户机的函数,因为它不会进行类型检查,而在当前的实现中,这是完全可能的

如果您关心较长的名称,我建议您最终查看
lens
库,该库旨在帮助您相对轻松地操作复杂的记录类型树


使用当前的实现,您还可以做一些与最终解决方案非常类似的事情:

isIndividualClient :: Client -> Bool
isIndividualClient (Individual _ _) = True
isIndividualClient _ = False

listIndividuals :: [Client] -> [Client]
listIndividuals clients = filter isIndividualClient clients

这里的主要区别是,
Individual
接受两个字段,因此我在模式中有两个
通配符匹配,
listIndividuals
的类型现在是
[Client]->[Client]
首先,我建议不要使用代数类型的记录类型,因为您最终使用了部分访问器函数。例如,让code
位置(个人(personal,“John”“Doe”Person)为True)是完全合法的
,但它会引发运行时错误。相反,考虑一些更像

data GovClient = GovClient {
    govName :: String
    } deriving Show

data CompanyClient = CompanyClient {
    companyName :: String,
    companyID :: Integer,        -- Also, don't overwrite existing names, `id` is built-in function
    companyContact :: String,
    companyPosition :: String
    } deriving Show

data IndividualClient = IndividualClient {
    indvFullName :: Person,
    indvOffers :: Bool
    } deriving Show
那你就可以

data Client
    = GovOrg GovClient
    | Company CompanyClient
    | Individual IndividualClient
    deriving (Show)
现在您还可以将函数定义为

isIndividualClient :: Client -> Bool
isIndividualClient (Individual _) = True
isIndividualClient _ = False

listIndividuals :: [Client] -> [IndividualClient]
listIndividuals clients = filter isIndividualClient clients
或者更无点形式的

listIndividuals = filter isIndividualClient
在这里,为了做出决定,我只是在一个单独的函数中使用模式匹配来确定使用了
客户机的构造函数中的哪一个。现在,您可以使用记录和代数类型的全部功能,只需再担心一点点代码,但安全性要高得多。例如,您永远不会意外地在单个客户机上调用一个需要政府客户机的函数,因为它不会进行类型检查,而在当前的实现中,这是完全可能的

如果您关心较长的名称,我建议您最终查看
lens
库,该库旨在帮助您相对轻松地操作复杂的记录类型树


使用当前的实现,您还可以做一些与最终解决方案非常类似的事情:

isIndividualClient :: Client -> Bool
isIndividualClient (Individual _ _) = True
isIndividualClient _ = False

listIndividuals :: [Client] -> [Client]
listIndividuals clients = filter isIndividualClient clients

<> P>主要区别在于,代码>个人<代码>两个字段,因此在模式中有两个<代码> > <代码>通配符匹配,并且 >类型的个人> <代码>现在是代码> [客户端] > [客户端] < /> > <代码> x==个人< /代码> -如果你来自C++,你应该知道这没有意义。Yea,我的想法很糟糕。这就像说
5==int
。但这似乎并不是因为,据我的理解,个体是一个值构造函数,而不是一个类型。所以我想我的问题更多的是如何检查客户的价值,“如何检查客户的价值”。模式匹配<代码> x==个人< /代码>——如果你来自C++,你应该知道这没有意义。Yea,我的想法不好。这就像说
5==int
。但这似乎并不是因为,据我的理解,个体是一个值构造函数,而不是一个类型。所以我想我的问题更多的是如何检查客户的价值,“如何检查客户的价值”。图案匹配灯,谢谢!我想我在以这种方式使用记录类型方面有点超前了。@mal20k如果您真的在API中确信永远不会执行非法操作,那么这样做很好,但通常我建议将其拆分为由单个ADT统一的单独类型。当然,它在您的记录上添加了一层额外的类型,但是您获得了很多安全性,并且在许多情况下,您的类型将更有意义。即使在这个小示例中,
listIndividuals
的类型也变得更能表达它实际在做什么。正是这些微小的差异需要经验来发现,所以不要对自己太苛刻!好的,谢谢!我想我在以这种方式使用记录类型方面有点超前了。@mal20k如果您真的在API中确信永远不会执行非法操作,那么这样做很好,但通常我建议将其拆分为由单个ADT统一的单独类型。当然,它在您的记录上添加了一层额外的类型,但是您获得了很多安全性,并且在许多情况下,您的类型将更有意义。即使在这个小示例中,
listIndividuals
的类型也变得更能表达它实际在做什么。就是这些小东西