制作可扩展的setter

时间:2018-04-20 16:51:32

标签: haskell functional-programming

我有一个看起来像这样的构造函数:

data FilterType 
  = OwnerId
  | OwnerIdReportType
  | ReportTypeProductType

filterParams = FilterParams
  { filterType :: FilterType
  , ownerId :: Maybe Id
  , productType :: Maybe ProductType
  , reportType :: Maybe ReportType
  }

然后当我设置过滤器参数时,我这样做,所以我只获取我需要的数据:

defaultFilterParams =
  { filterType = OwnerId
  , ownerId = Nothing
  , productType = Nothing
  , reportType = Nothing
  }

mkFilterParams :: FilterType -> Id -> IO FilterParams
mkFilterParams OwnerId reportId = do
  ownerId <- getOwnerId reportId
  defaultFilterParams { ownerId }
mkFilterParams ReportTypeProductType reportId = do
  reportType <- getReportType reportId
  productType <- getProductType reportId
  defaultFilterParams
    { filterType = ReportTypeProductType
    , productType
    , reportType
    }
-- etc

显然,随着更多FilterTypes的添加,mkFilterParams开始包含大量重复的代码。是否有更有效/可扩展的方式来实现这一点?我已经盯着它看了很长时间才能看到新的方式。

2 个答案:

答案 0 :(得分:4)

我的另一个答案讨论了一个非常简单的解决方案,即Haskell2010;这具有易于理解和易于使用的优点。但它确实有一个有趣的重复构造函数名称。在这个答案中,我将简要介绍如何使用GADT来避免这个问题。 GADT为我们提供了一种将术语构造函数与类型级别连接起来的方法。所以:

data FilterType a where
    Owner :: FilterType Id
    Product :: FilterType ProductType
    ReportProduct :: FilterType (ReportType, ProductType)

然后我们可以为每种类型的过滤器返回不同的信息:

mkFilter :: FilterType a -> ReportId -> IO a
mkFilter Owner = getOwnerId
mkFilter Product = getProductId
mkFilter ReportProduct = \reportId -> (,) <$> getReportType reportId
                                          <*> getProductType reportId

据推测,要使用其中之一,我们需要将FilterType aa捆绑在一起;例如也许你会有一个像这样的类型的函数:

filterMatches :: FilterType a -> a -> Record -> Bool

这样的捆绑甚至可能值得在其自己的存在类型中形式化:

data Filter where Filter :: FilterType a -> a -> Filter

答案 1 :(得分:3)

这样的事情怎么样?

data FilterType = OwnerT | ProductT | ReportProductT
data Filter
    = Owner Id
    | Product ProductType
    | ReportProduct ReportType ProductType

mkFilter :: FilterType -> ReportId -> IO Filter
mkFilter ty reportId = case ty of
    OwnerT -> Owner <$> getOwnerId reportId
    ProductT -> Product <$> getProductType reportId
    ReportProductT -> ReportProduct <$> getReportType reportId
                                    <*> getProductType reportId

如果重复的reportId困扰您,您可以考虑使用类似的类型:

mkFilter :: FilterType -> ReaderT ReportId IO Filter

如果您随后修改了getOwnerIdgetReportTypegetProductType以使用ReaderT,这可能会感觉最干净。