【问题标题】:Combining methods in ClojureClojure 中的组合方法
【发布时间】:2014-08-28 10:54:59
【问题描述】:

假设我们有一个多方法foo。它有几个实现。假设当foo 的参数是包含字符\r 的字符串时调用其中一个,而当foo 的参数是包含字符\! 的字符串时执行另一个。伪代码:

(defmulti foo ???) ; can't come up with function..

(defmethod foo \r [_]
  (println "one"))

(defmethod foo \! [_]
  (println "two"))

所以当我们这样调用我们的函数时:

(foo "right!") ;; desired output:
one
two
;; => nil

这里重要的是支持的方法列表不应该是死板的,而是可以扩展的,所以以后可以添加新的方法而不用接触原始代码。

虽然最近几天我的 Clojure 技能有了显着提高,但我仍然缺乏经验。我最好的想法是保留一张带有“字符 - 功能”对的地图,然后手动遍历它并执行正确的功能。在这种情况下,我还需要一些接口来注册新功能等。什么是惯用的解决方案?

【问题讨论】:

    标签: clojure multimethod


    【解决方案1】:

    我认为多方法的工作方式与您期望的不同。

    也就是说:对于单个多方法调用,多方法中的调度仅调用一次,因此无法获得您期望的结果(“正确!”作为参数打印“一”和“二”),除非您定义一种实现,该实现实际上处理在输入字符串中同时包含\r\! 的情况并打印所需的输出。

    这不容易扩展。

    实现您想要的更好的方法是通过迭代输入字符串显式地进行多次调用:

    ; You want the dispatch function to just return the character passed to it.
    (defmulti foo identity) 
    
    ; The argument list here is mandatory, but we don't use them at all, hence '_'
    (defmethod foo \r [_] 
      (println "one"))
    
    (defmethod foo \! [_]
      (println "two"))
    
    
    ; You need the default case for all the other characters
    (defmethod foo :default [_]
      ())
    
    ; Iterates the string and executes foo for each character
    (defn bar [s] 
        (doseq [x s] 
            (foo x)))
    

    打电话来

    (bar "right!") 
    

    将打印:

    one
    two
    

    编辑

    如果您需要访问多方法体内的整个字符串,则将其与字符一起显式传递:

    ; You want the dispatch function to just return the character passed to it as the first arg.
    (defmulti foo (fn [c _] c)) 
    
    
    (defmethod foo \r [c s] 
      (println "one"))
    
    (defmethod foo \! [c s]
      (println "two"))
    
    ; The default now takes two arguments which we ignore
    (defmethod foo :default [_ _] ())
    
    ; Iterates the string and executes foo for each character
    (defn bar [s] 
        (doseq [x s] 
            (foo x s)))
    

    【讨论】:

    • i) 我知道使用多方法是不可能完成的(我读过一本书!;-),但多方法是我想到的最接近的抽象。 ii) 我担心我不能使用你的解决方案,因为 \r\! 的方法必须可以访问整个字符串。
    • @Mark 所以你需要访问多方法中的整个字符串?
    • 是的,就是这样。将\r\! 视为一些论点。我希望有几种方法,并且每种方法都专门用于处理整个对象,前提是它具有一定的质量。所以包含\r的字符串会触发一种方法,包含\!的字符串会触发另一种方法,同时包含这两种方法的字符串会同时触发两种方法,而且可能还有更多。
    【解决方案2】:

    一个简单的函数列表将允许任意条件。如果您正在处理字符串,正则表达式也可以让您的生活更简单:

    ;; start with some functions
    (defn on-r [x]
      (when (re-find #"r" x)
        "one"))
    (defn on-! [x]
      (when (re-find #"!" x)
        "two"))
    (def fns (atom [on-r on-!]))
    
    ;; call all functions on some value
    (keep #(% "right!") @fns)
    => ("one" "two")
    (keep #(% "aaaa") @fns)
    => ()
    
    ;; later add more functions
    (defn on-three [x]
      (when (= 3 (count x))
        "three"))
    (swap! fns conj on-three)
    (keep #(% "bar") @fns)
    => ("one" "three")
    
    ;; or just use different rules altogether
    (def other-fns [#(when (rand-nth [true false])
                       (str % (rand-int 10)))
                    #(when (nil? %) "nil")])
    (keep #(% nil) other-fns)
    => ("3" "nil")
    

    【讨论】:

    • 对不起,这只是一个例子。我对在 Clojure 中组合方法很感兴趣,而不是解决这个特定问题。此外,正如我所说的“重要的是,支持的方法列表不应该是死板的,而是可扩展的,因此以后可以添加新方法而无需触及原始代码。”
    • 嗨,马克,感谢您的澄清。为了解释我的想法,函数列表并不一定要使用一组预定义的条件。您可以修改列表,或使用不同的列表或列表的组合。我已经修改了我的答案来证明这一点。我的观点是,要检查一堆关于某个值的东西,多方法是一个有用但正交的概念,有趣的组合技术是函数组合。当然,这只是我的看法,我知道您对探索严格的多方法更感兴趣。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-02
    • 1970-01-01
    • 2021-10-26
    • 2016-06-08
    • 1970-01-01
    • 1970-01-01
    • 2019-11-02
    相关资源
    最近更新 更多