【问题标题】:Managing invariants in Clojure / Haskell在 Clojure / Haskell 中管理不变量
【发布时间】:2015-02-07 10:18:20
【问题描述】:

我一直在比较 OOP 和 FP 方法,但我无法专注于函数式编程中的一件事 - 在数据结构中保持不变量。

例如想象以下要求。

我们有一个项目列表,每个项目都有任务列表和分配成员列表。 每个任务都可以分配一个工作人员,但只能从项目分配成员列表中分配。

我可以想象如何在 OOP 语言中解决这个问题,例如在 Java 中,通过添加必需的检查和异常,我认为这会导致代码更健壮。

但是由于数据与 FP 中的行为分离,我应该如何解决 FP 中的相同问题,比如在 Clojure 或 Haskell 中?

【问题讨论】:

  • 需要注意的是,Clojure 和 Haskell 都默认使用不可变的数据结构。这意味着在您验证数据后,任何客户端都无法更改数据,这意味着验证可以是对首次提交的简单预检查,并且每次使用时不需要任何特殊的更新方法或断言。

标签: haskell clojure functional-programming invariants


【解决方案1】:

在 Clojure 中,可以在任何函数上指定任意 :pre:post 条件。这是来自documentation 的示例:

(defn constrained-sqr [x]
    {:pre  [(pos? x)]
     :post [(> % 16), (< % 225)]}
    (* x x))

还有一个非常有趣的库core.contracts 在 Clojure 中实现了contracts

【讨论】:

    【解决方案2】:

    您的问题非常笼统,可以使用很多策略来解决相关问题,但本质上,检查不变量(在运行时)比在 OOP 中“相等”

    assign :: Task -> Worker -> Either String Task
    assign task worker =
        if not (taskProject task) `containsWorker` worker
            then Left "You can't assign..."
            else Right $ task { taskWorkers = worker : taskWorkers task }
    

    一种常见的行为是隐藏data 构造函数(如TaskWorkerProject),OOP 对应的是写private 构造函数。

    module Scheduler (
      Task   -- instead `Task (..)`
    , Worker -- instead `Worker (..)`
    ...
    , assign
    , createTask
    , createWorker
    ...
    ) where
    

    (我不知道当前 Haskell 对 friendprotected 的支持,...对应的可能不存在,你可以找到很多带有私有对象的 Some.Module.Internals.Something 的 Haskell 模块)

    主要问题是如何构建整个程序以实现所需的行为。

    Real World Haskell 是了解这一点的一个很好的起点,或者按照相关问题 Large-scale design in Haskell? 的建议

    另一方面,关于 Haskell 中的前置/后置条件,您可以阅读 What are the options for precondition checking in Haskell

    【讨论】:

    • 在这个例子中我明白当我有一个任务时,我可以编写自己的分配函数并创建任务的无效变体?我的问题是,在 OOP 中我可以给出(返回)一个任务(对象),当它被正确实现时,我可以假设不可能对其状态进行无效修改。当然,在 FP 中,不会改变变量的(内部)状态,但我是否也可以阻止从它派生的新变量处于一致状态?
    • "我也可以",是的,存在多种封装方式; @MathematicalOrchid 响应指向它(我更新了响应)。
    【解决方案3】:

    您说数据与 FP 语言中的行为“分离”,但在 Haskell(我不知道 Clojure)中,您可以很容易地在模块中定义数据结构,将其定义设为私有,并且仅将函数导出到操纵数据。

    换句话说,Haskell 没有(OO 风格的)类,但它仍然有封装

    【讨论】:

    • 好的,但是当我想要例如评估数据时如何从模块中获取数据,例如打印出项目任务列表?我是否必须为此目的导出不同的数据结构?
    • 您可以导出数据结构,但不能导出其内部结构。这更像是一个有私人成员的班级。除了它被称为模块,而不是类。
    【解决方案4】:

    JoseJuan 和 MathematicalOrchid 讨论了在公开类型和接口的同时隐藏构造函数的关键点,但是在 Haskell 中还有另一种管理某些类型不变量的技术:在类型系统中对它们进行编码。代数数据类型在一定程度上是自行完成的:

    data Tree a = Tri (Tree a) a (Tree a) a (Tree a)
                | Bin (Tree a) a (Tree a)
                | Tip
    

    更受限制
    newtype Tree a = Tree [a] [Tree a]
    

    但您可以使用嵌套类型、幻像类型和 GADT 走得更远。例如,Data.Sequence 定义

    newtype Elem a = Elem a
    data Node a = Node2 !Int a a | Node3 !Int a a a
    data Digit a = One a | Two a a | Three a a a | Four a a a a
    data FingerTree a = Empty
                      | Single a
                      | Deep !Int (Digit a)
                        (FingerTree (Node a))
                        (Digit a)
    newtype Seq a = Seq (FingerTree (Elem a))
    

    请注意,深层FingerTree a 包含FingerTree (Node a)。这称为“嵌套”或“非常规”类型;它确保每一层的 2-3 棵树比上一层的树深一棵。

    使用幻像类型和 GADT 可以以不同方式维护相同的形状不变量(但效率较低,事实证明):

    {-# LANGUAGE GADTs, DataKinds, KindSignatures #-}
    data Nat = Z | S Nat
    
    -- This is a GADT; n is a phantom type
    data Tree23 (n::Nat) a where
      Elem :: a -> Tree23 Z a
      Node2 :: !Int -> Tree23 n a -> 
                 Tree23 n a -> Tree23 (S n) a
      Node3 :: !Int -> Tree23 n a -> Tree23 n a ->
                 Tree23 n a -> Tree23 (S n) a
    
    -- n is again a phantom type
    data Digit (n::Nat) a
      = One (Tree23 n a)
      | Two (Tree23 n a) (Tree23 n a)
      | Three (Tree23 n a) (Tree23 n a) (Tree23 n a)
      | Four (Tree23 n a) (Tree23 n a) (Tree23 n a) (Tree23 n a)
    
    -- n is still a phantom type
    data FingerTree (n::Nat) a
         = Empty
         | Single a
         | Deep !Int (Digit n a) (FingerTree (S n) a) (Digit n a)
    

    在这个版本中,手指树的级别是使用幻像类型“跟踪”的,然后使用 GADT 强制 2-3 棵树的高度与之匹配。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-05
      • 1970-01-01
      • 1970-01-01
      • 2022-10-08
      • 1970-01-01
      • 2014-09-16
      相关资源
      最近更新 更多