在Clojure中使用索引保持状态查找表的惯用方法

Joe*_*per 7 java state clojure hashmap

我对Clojure和函数式编程相当新,我一直在努力解决以下问题.我想为一系列令牌(字符串)分配一个唯一且稳定的索引.由于查找比插入更多,因此哈希映射似乎是要走的路.

在Java中,我会写一些东西

int last = 0; 
HashMap<String, Integer> lut = new HashMap<String, Integer>();

function Integer getIndex(String token) {
   Integer index = lut.get(token); 
   if(index == null) 
      last++;
      lut.put(token, last);
      return last;
    else { 
      return index;
    }
}
Run Code Online (Sandbox Code Playgroud)

Clojure中的音译版本就像

(def last-index (atom 0))
(def lookup-table (atom {}))

(defn get-index [token]
  (if (nil? (get @lookup-table token))
    (do 
      (swap! last-index inc)
      (swap! lookup-table assoc token @last-index)
      @last-index)
    (get @lookup-table token)))
Run Code Online (Sandbox Code Playgroud)

但这似乎并不是非常自我,因为它基本上是副作用,甚至没有隐藏它.

那么如果没有两个原子来保持状态,你会怎么做呢?

ama*_*loy 3

Ankur 给出的答案不是线程安全的,尽管我不认为 seh 对原因的描述很有帮助,而且他的替代方案更糟糕。可以合理地说“好吧,我现在不担心多线程”,在这种情况下,答案就很好。但是,即使您在任何特定情况下都不需要这种保证,能够安全地编写此类内容也是很有价值的,唯一安全的方法是在 内部执行所有逻辑swap!,如下所示:

(let [m (atom {})]
  (defn get-index [token]
    (get (swap! m
                #(assoc % token (or (% token) (count %))))
         token)))
Run Code Online (Sandbox Code Playgroud)

swap!您可以通过避免调用函数时如果已经存在条目来加快速度,并且如果输入后已经存在条目则避免关联,swap!但您必须“仔细检查”映射在分配当前令牌之前没有条目(count %),因为在您开始swap!ing 之前(但在您决定swap!,并为当前令牌分配了一个值,在这种情况下您必须尊重该任务而不是制定新任务。

编辑:顺便说一句,Java 版本当然也有同样的线程安全问题,因为默认情况下 Java 中的所有内容都是可变的并且不是线程安全的。至少在 Clojure 中你必须放一个!在那里放一个 ,说“是的,我知道这很危险,我知道我在做什么。”

所以从某种意义上说,Ankur 的解决方案是Java 代码的完美翻译,但更好的是改进它!