Java 在Clojure中保存带索引的有状态查找表的惯用方法
一般来说,我对Clojure和函数式编程相当陌生,我一直在努力解决以下问题。我想为一系列标记(字符串)分配一个唯一且稳定的索引。由于将有比插入更多的查找,因此哈希映射似乎是一种可行的方法 在Java中,我会按照Java 在Clojure中保存带索引的有状态查找表的惯用方法,java,clojure,hashmap,state,Java,Clojure,Hashmap,State,一般来说,我对Clojure和函数式编程相当陌生,我一直在努力解决以下问题。我想为一系列标记(字符串)分配一个唯一且稳定的索引。由于将有比插入更多的查找,因此哈希映射似乎是一种可行的方法 在Java中,我会按照 int last = 0; HashMap<String, Integer> lut = new HashMap<String, Integer>(); function Integer getIndex(String token) { Integer
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;
}
}
但这似乎不是很像我,因为它基本上是副作用,甚至没有隐藏它
那么,在没有两个原子保持状态的情况下,您如何做到这一点呢?原子中的单个贴图就足够了:
(def m (atom {}))
;adding new string to map
(swap! m #(assoc %1 "Hello" (count %)))
;get an index
(@m "Hello")
(defn get-index [token]
(or (@m token)
((swap! m #(assoc %1 token (count %))) token)))
您基本上试图将Java命令式代码映射到clojure,这就是为什么您在问题中得到了该解决方案。试着从组合表达式的角度来思考,而不是从循序渐进的命令式风格来思考。Ankur给出的答案不是线程安全的,尽管我不认为seh对原因的描述很有帮助,他的选择更糟糕。说“我现在不担心多线程”是有道理的,在这种情况下,答案很好。但是能够安全地编写这样的东西是很有价值的,即使在任何特定情况下都不需要这种保证,唯一安全的方法是在
交换中执行所有逻辑代码>,如下所示:
(let [m (atom {})]
(defn get-index [token]
(get (swap! m
#(assoc % token (or (% token) (count %))))
token)))
您可以通过避免交换来加快速度代码>如果在调用函数时已经有一个条目,并且在您输入交换后,如果已经有条目,则避免关联代码>,但您必须“仔细检查”映射是否没有当前令牌的条目,然后才分配它(count%)
,因为在您开始交换之前,可能有其他线程潜入
ing(但在您决定swap!
之后),并为当前令牌分配了一个值,在这种情况下,您必须尊重该分配,而不是进行新的分配
编辑:顺便说一下,Java版本当然也有同样的线程安全问题,因为默认情况下Java中的所有内容都是可变的,而不是线程安全的。至少在Clojure中,您必须输入一个在那里,说“是的,我知道这很危险,我知道我在做什么。”
所以从某种意义上说,Ankur的解决方案是Java代码的完美翻译,但更好的是改进它 值得注意的是,您永远不希望有这样的代码来修改相互依赖的两个原子。原子是独立态。如果需要对相互依赖的多个变量进行变异,则应使用refs和dosync。考虑到Ankur的回答,在这里并不是特别相关,但要记住一点。你为什么建议使用swap代码>而不是比较并设置
,甚至在dosync
调用中重新绑定映射?如果您希望与其他线程竞争,请使用swap代码>这里允许在不知情的情况下用你的更改覆盖他们的更改。我很好奇你认为我的便条会让事情变得更糟。我很简短,但我的观点是,在这里无条件地重新绑定atom是不安全的,因为两个正在运行的线程这样做会放弃另一个线程试图进行的更改。如何协调这两条赛道是设计问题。重新阅读交换的文档代码>今天,我看到它在内部旋转,直到它一致地设置一个值,因此我建议使用比较并设置代码>将是过分的。我仍然认为对ref使用dosync
是最清晰的方法。两个线程竞相使用(swap!af)
永远不要“丢弃”更改。原子将始终处于一致状态,并且最终a
将设置为(f(f old-a))
。问题是,他正在根据swap外部的信息构建f1
和f2
代码>可能已过期<代码>比较和交换在我看来,代码>只会让犯这个错误变得更容易。dosync
是可以的,只要你确保它是一个ref而不是atom,但是管理一个引用和知道如何编写一个好的swap通常是过火了代码>很重要。
(let [m (atom {})]
(defn get-index [token]
(get (swap! m
#(assoc % token (or (% token) (count %))))
token)))