gpt4 book ai didi

haskell - 对于更多类型安全的 DSL,可绑定(bind)仿函数是否是有用的抽象?

转载 作者:行者123 更新时间:2023-12-04 15:13:08 25 4
gpt4 key购买 nike

动机

我目前正在做一个小爱好项目来尝试实现类似 TaskJuggler 的东西。在 Haskell 中,主要是作为编写特定领域语言的实验。

我目前的目标是有一个小型 DSL 来构建 Project 的描述。 ,连同它相关的 Task s。还没有层次结构,尽管这将是我的下一个扩展。目前,我有以下数据类型:

data Project = Project { projectName :: Text
, projectStart :: Day
, projectEnd :: Day
, projectMaxHoursPerDay :: Int
, projectTasks :: [Task]
}
deriving (Eq, Show)

data Task = Task { taskName :: Text }
deriving (Eq, Show)

没什么太疯狂的,我相信你会同意的。

现在我想创建一个 DSL 来构建项目/任务。我可以使用 Writer [Task] monad 来构建任务,但这不会很好地扩展。我们现在或许可以做到以下几点:
project "LambdaBook" startDate endDate $ do
task "Web site"
task "Marketing"

在哪里 project :: Text -> Date -> Date -> Writer [Task] a ,它运行 Writer获取任务列表,并为 projectMaxHoursPerDay 选择默认值,例如 8 .

但我以后希望能够做类似的事情:
project "LambdaBook" $ do
maxHoursPerDay 4
task "Web site"
task "Marketing"

所以我使用 maxHoursPerDay指定关于 Project 的( future )属性.我不能再使用 Writer为此,因为 [Task]无法捕获我需要的一切。

我看到解决这个问题的两种可能性:

将“可选”属性分离到它们自己的 monoid 中

我可以拆分 Project进入:
data Project = Project { projectName, projectStart, projectEnd, projectProperties }
data ProjectProperties = ProjectProperties { projectMaxHoursPerDay :: Maybe Int
, projectTasks :: [Task]
}

现在我可以拥有一个实例 Monoid ProjectProperties .当我运行 Writer ProjectProperties我可以完成构建 Project 所需的所有默认设置.我想没有理由 Project需要嵌入 ProjectProperties - 它甚至可以具有与上述相同的定义。

使用可绑定(bind)仿函数 Semigroup m => Writer m
Project不是 Monoid ,当然可以做成 Semigroup .名称/开始/结束是 First , maxHoursPerDayLast , 和 projectTasks[Task] .我们不能有 Writer单子(monad)超过 Semigroup , 但我们可以有一个 Writer可绑定(bind)仿函数。

实际问题

使用第一个解决方案 - 专用“属性” Monoid - 我们可以选择成本来使用单子(monad)的全部功能。我可以复制 Project 中的可覆盖属性和 ProjectProperties ,后者将每个属性包装在适当的幺半群中。或者我可以只编写一次幺半群并将其嵌入到 Project 中- 虽然我放弃了类型安全( maxHoursPerDay 必须是 Just 当我实际制定项目计划时!)。

可绑定(bind)仿函数消除了代码重复并保留了类型安全性,但代价是立即放弃语法糖,并且可能长期使用起来很痛苦(由于缺少 return/ pure ) .

我在 http://hpaste.org/82024 有两种方法的示例(对于可绑定(bind)仿函数)和 http://hpaste.org/82025 (对于单子(monad)方法)。这些示例超出了这篇 SO 帖子中的内容(已经足够大了),并且有 Resource连同 Task .希望这能说明为什么我需要走这么远 Bind (或 Monad )在 DSL 中。

我很高兴甚至找到了可绑定(bind)仿函数的适用用途,所以我很高兴听到您可能有的任何想法或经验。

最佳答案

data Project maxHours = Project {tasks :: [Task], maxHourLimit :: maxHours}

defProject = Project [] ()

setMaxHours :: Project () -> Project Double
setMaxHours = ...

addTask :: Project a -> Project a

type CompleteProject = Project Double...

runProject :: CompleteProject -> ...

storeProject :: CompleteProject -> ...

您现在需要函数组合,而不是编写器中的操作,但是这种模式允许您从部分填充的记录开始,并设置那些需要设置一次且仅一次的内容,并且具有足够的类型安全性。它甚至可以让您对最终结果中的各种设置值和未设置值之间的关系施加约束。

关于haskell - 对于更多类型安全的 DSL,可绑定(bind)仿函数是否是有用的抽象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14788835/

25 4 0