Clojure 允许封装和继承,但是我可以将它们结合起来吗?

发布于 2024-08-06 21:16:04 字数 838 浏览 5 评论 0原文

这是一个过于简单的示例来进行说明:

我可以封装一个实现细节,例如使用原子作为计数器:

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))})))

但这意味着我需要重新定义所有内容以添加功能(无继承):

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))
      :-- (fn [] (swap! c dec))})))

而如果可以只扩展一个函数:

(assoc c :-- (env (:++ c) (fn [] (swap! c dec))))

(def c (make-counter))
(def b (make-bi-counter))
user=> ((:-- b))
-1
user=> ((:-- b))
-2
user=> ((:get b))
-2

或者我可以只暴露原子并具有独立的函数:

(defn -- [a] (swap! a dec))
(def a (atom 0))
(-- a)

如果需要“继承”(或者更准确地说:扩展),那么似乎最好的选择是放弃封装。

Here is an overly simplistic example for illustration:

I can encapsulate an implementation detail such as using an atom for a counter:

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))})))

But that means I need to redefine everything to add a feature (no inheritance):

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))
      :-- (fn [] (swap! c dec))})))

Whereas if it were possible to just extend the one function:

(assoc c :-- (env (:++ c) (fn [] (swap! c dec))))

(def c (make-counter))
(def b (make-bi-counter))
user=> ((:-- b))
-1
user=> ((:-- b))
-2
user=> ((:get b))
-2

Or I could have just exposed the atom and had independent functions:

(defn -- [a] (swap! a dec))
(def a (atom 0))
(-- a)

It appears the best option is to forgo encapsulation, if 'inheritance' (or perhaps more accurately: extension) is desirable.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

残疾 2024-08-13 21:16:04

是的,我认为惯用的 Clojure 是将数据与函数分开,正是因为您以后可以编写新函数来处理旧数据。

将函数与数据捆绑在一起还意味着您以后无法在不更改或重新生成所有数据结构的情况下更改函数,因为这些匿名函数将存储在各处。在 REPL 上进行交互式开发,我讨厌每次更改函数时都必须寻找所有数据结构来修复它们。哈希映射中的闭包很聪明,但它们非常脆弱,除非有充分的理由,否则我不会走这条路。

只需要一点点的纪律来定义你的接口(作为函数),然后记住坚持你的接口,而不是直接与原子打交道。目前还不清楚强行向自己隐瞒事情会给你带来什么好处。

如果您想要继承,多方法是一个好方法。

(defmulti getc type)
(defmulti ++ type)
(defmulti -- type)

(derive ::bi-counter ::counter)

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::counter})))

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::bi-counter})))

(defmethod getc ::counter [counter]
  @counter)

(defmethod ++ ::counter [counter]
  (swap! counter inc))

(defmethod -- ::bi-counter[counter]
  (swap! counter dec))

例如

user> (def c (make-counter))
#'user/c
user> (getc c)
0
user> (def b (make-bi-counter))
#'user/b
user> (++ c)
1
user> (++ b)
1
user> (-- b)
0
user> (-- c)
; Evaluation aborted.
;;  No method in multimethod '--' for dispatch value: :user/counter

Yes, I think idiomatic Clojure is to separate your data from your functions, for precisely the reason that you can later write new functions to work on the old data.

Bundling functions with data also means you can't alter your functions later without altering or re-generating all of your data structures, since you'll have these anonymous functions stored all over the place. Developing interactively at a REPL, I'd hate to have to hunt down all of my data structures to fix them every time I change a function. Closures in hash-maps are clever but they're pretty fragile and I wouldn't go that route unless there was a really good reason.

It only takes a modicum of discipline to define your interface (as functions) and then remember to stick to your interface and not to mess around with the atom directly. It's unclear what benefit you're going to get from forcibly hiding things from yourself.

If you want inheritance, multimethods are a good way to do it.

(defmulti getc type)
(defmulti ++ type)
(defmulti -- type)

(derive ::bi-counter ::counter)

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::counter})))

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::bi-counter})))

(defmethod getc ::counter [counter]
  @counter)

(defmethod ++ ::counter [counter]
  (swap! counter inc))

(defmethod -- ::bi-counter[counter]
  (swap! counter dec))

e.g.

user> (def c (make-counter))
#'user/c
user> (getc c)
0
user> (def b (make-bi-counter))
#'user/b
user> (++ c)
1
user> (++ b)
1
user> (-- b)
0
user> (-- c)
; Evaluation aborted.
;;  No method in multimethod '--' for dispatch value: :user/counter
久而酒知 2024-08-13 21:16:04

我确信这不是惯用的 Clojure,但你绝对可以模拟受保护的变量。

在您的示例中 c 是一个模拟私有变量。如果您希望它成为受保护的变量,则需要以允许 ma​​ke-counterma​​ke-bi-counter 访问它的方式定义它。将名为 secret 的哈希传递到 ma​​ke-counter 中。如果 secret 包含 c,请使用它。否则创建您自己的。

然后ma​​ke-bi-counter可以创建一个包含csecret对象并将其传递给ma​​ke-counter构造函数。现在 ma​​ke-bi-counterma​​ke-counter 都可以访问相同的 c

I'm sure it is not idiomatic Clojure but you can definitely simulate protected variables.

In your example c is a simulated private variable. If you want it to be a protected variable, you need to define it in a way that allows both make-counter and make-bi-counter to access it. Pass a hash called secret into make-counter. If secret contains c, use that. Create your own otherwise.

Then make-bi-counter can create a secret object which contains c and pass it into the make-counter constructor. Now both make-bi-counter and make-counter have access to the same c.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文