假设我们有一个商店管理应用程序.它有Customer
s和罐头chargeFee()
.但它应该仅对活动Customer
s 这样做.
我见过这种常见的方式(Java /伪代码)是这样的:
class Customer {
String name
StatusEnum status // 1=active, 2=inactive
}
// and this is how the customers are charged
for (c:Customer.listByStatus(StatusEnum.1)) {
c.chargeFee()
}
Run Code Online (Sandbox Code Playgroud)
这没关系,但它并不能阻止某人从非活动状态收取费用Customer
.即使chargeFee()
检查状态Customer
,也就是运行时错误/事件.
因此,考虑到整个"使非法国家无法代表"的事情,如何设计这个应用程序(例如在Haskell中)?如果有人试图向不活跃的客户收费,我想要编译错误.
我在想这样的事情,但我仍然不允许我限制,chargeFee
以便Customer
不能收取非活动费用.
data CustomerDetails = CustomerDetails { name :: String }
data Customer a = Active a | Inactive a
chargeFee :: Active a -> Int -- this doesn't work, do I need DataKinds?
Run Code Online (Sandbox Code Playgroud)
你可以使用幻像类型完成这样的事情:
module Customer
(CustomerKind(..), Customer, {- note: MkCustomer is not exported -}
makeCustomer, activate, chargeFee) where
data CustomerKind = Active | Inactive
data Customer (x :: CustomerKind) = MkCustomer String
mkCustomer :: String -> Customer Inactive
mkCustomer = MkCustomer
-- perhaps `IO (Customer Active)' or something else
activate :: Customer Inactive -> Maybe (Customer Active)
activate = ...
chargeFee :: Customer Active -> Int
chargeFee = ...
Run Code Online (Sandbox Code Playgroud)
这activate
将以某种方式确保给定的客户可以被激活(并且这样做),从而产生所述活跃的客户.但是试图调用chargeFee (mkCustomer ...)
是一种类型错误.
请注意,DataKinds
并非严格要求 - 以下内容相同:
data Active
data Inactive
-- everything else unchanged
Run Code Online (Sandbox Code Playgroud)
通过简单地声明两种类型 - ActiveCustomer
而且InactiveCustomer
- 幻像类型方法允许您编写不关心客户类型的函数,可以在没有幻像类型的情况下完成相同的操作:
customerName :: Customer a -> String
customerName (MkCustomer a) = ...
Run Code Online (Sandbox Code Playgroud)
您始终可以对非法行为进行chargeFee
退货Maybe
或退货:Either
chargeFee :: Customer a -> Maybe Int
chargeFee (Inactive _) = Nothing
chargeFee (Active cust) = ...
Run Code Online (Sandbox Code Playgroud)