使用类型强制正确

zor*_*119 6 haskell

假设我们有一个商店管理应用程序.它有Customers和罐头chargeFee().但它应该仅对活动Customers 这样做.

我见过这种常见的方式(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)

use*_*038 7

你可以使用幻像类型完成这样的事情:

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)


Cha*_*ert 0

您始终可以对非法行为进行chargeFee退货Maybe或退货:Either

chargeFee :: Customer a -> Maybe Int
chargeFee (Inactive _) = Nothing
chargeFee (Active cust) = ...
Run Code Online (Sandbox Code Playgroud)

  • 但是编译器如何知道哪些客户是活跃的呢?这要等到运行时才能决定,不是吗? (3认同)