haskell - 遍历多态结构并仅在少数情况下执行转换

标签 haskell generic-programming scrap-your-boilerplate

假设我们用以下方式表示公司层次结构:

{-# LANGUAGE DeriveDataTypeable #-}

import           Data.Data
import           Data.Generics.Aliases
import           Data.Generics.Schemes

data CompanyAsset = Employee Name Salary
                  | Plant Name
                  | Boss Name Performance Salary [CompanyAsset]
                  | Pet Name
                  | Car Id
                  | Guild [CompanyAsset]
                  | Fork CompanyAsset CompanyAsset
                  -- ... and imagine 100 more options that recursively use `CompanyAsset`.
                  deriving (Show, Data)

-- Performance of the department.
data Performance = Good | Bad deriving (Show, Data)

type Name = String

type Id = Int

newtype Salary = Salary Double deriving (Show, Data, Typeable)

raise :: Salary -> Salary

我想定义一个函数来提高没有 Boss 祖先部门有 Bad 绩效的公司 Assets 的工资。这样的函数可以很容易地定义如下:

raiseSalaries :: CompanyAsset -> CompanyAsset
raiseSalaries (Boss n Good s as) = Boss n Good (raise s) (raiseSalaries <$> as)
raiseSalaries a@(Boss _ Bad _ _) = a -- The salaries of everything below are not raised if the performance is 'Bad'
raiseSalaries ... -- and from here onwards we have **boilerplate**!

问题是这需要大量的样板文件(为了便于讨论,请假设 CompanyAsset 已给出且无法更改)。

所以我的问题是是否有一种遍历数据结构的方法可以避免上面的样板文件。

此问题与 similar one 有关我发布了,但在这种情况下使用 everywhere'无济于事,因为在某些情况下不应提高工资。

最佳答案

这可以通过 Traversal 来完成对于 CompanyAsset。可以自己写,也可以用uniplateplate来自镜头。

为了说明,我将明确地为 CompanyAsset 编写一个遍历。它将一个操作(我称之为 p,在 pure 中)应用于公司 Assets 的每个直接后代。请注意 traverse_ca pure == pure

traverse_ca :: Applicative f => (CompanyAsset -> f CompanyAsset) -> CompanyAsset -> f CompanyAsset
traverse_ca p ca =
  case ca of
    Fork ca1 ca2      -> Fork <$> p ca1 <*> p ca2
    Boss n perf s cas -> Boss n perf s <$> traverse p cas
    Guild cas         -> Guild <$> traverse p cas
    otherwise         -> pure ca

这本身就足以定义 raiseSalaries,无需任何额外的样板文件。

import Data.Functor.Identity

raiseSalaries :: CompanyAsset -> CompanyAsset
raiseSalaries (Boss n Good s as) = Boss n Good (raise s) (raiseSalaries <$> as)
raiseSalaries a@(Boss _ Bad _ _) = a -- The salaries of everything below are not raised if the performance is 'Bad'
raiseSalaries a = runIdentity $ traverse_ca (pure . raiseSalaries) a

关于haskell - 遍历多态结构并仅在少数情况下执行转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47327889/

相关文章:

c++ - 模板类中 `is_base_of` 的静态断言因 MSVC 中的意外类型而失败

haskell - 什么是 "Scrap Your Boilerplate"?

haskell - 如何为三级类型构造函数 (ext3) 的类型扩展定义 SYB 函数?

haskell - 使用 Haskell fork 到 shell 脚本并终止原始进程

haskell - 刚性类型变量不匹配

Haskell 的 DataKinds 以及值、类型和种类的关系

haskell - 使用不同参数调用时模板 Haskell 编译错误

r - R 中的二元运算符/中缀函数是通用的吗?以及如何利用?

reflection - Go 中的泛型编程。避免硬编码类型断言

haskell - SYB(废弃您的样板)相对于 GHC 泛型的优势