具有概率字段的Clojure记录的统一访问
是否有一种优雅的方法来创建和使用包含常量和概率字段的记录。我希望能够像下面这样做,其中“sampler”是一个函数,它返回来自某个分布的样本。其目标是使访问的字段是常量还是概率,对用户透明具有概率字段的Clojure记录的统一访问,clojure,records,Clojure,Records,是否有一种优雅的方法来创建和使用包含常量和概率字段的记录。我希望能够像下面这样做,其中“sampler”是一个函数,它返回来自某个分布的样本。其目标是使访问的字段是常量还是概率,对用户透明 > (defrecord Stat [val1 val2]) > (def s1 (Stat. 1 sampler)) > (:val1 s1) > 1 > (:val2 s1) > 4 > (:val2 s1) > 2 对于自定义deftype
> (defrecord Stat [val1 val2])
> (def s1 (Stat. 1 sampler))
> (:val1 s1)
> 1
> (:val2 s1)
> 4
> (:val2 s1)
> 2
对于自定义deftypes,让关键字查找以一种不同于在映射中查找固定值的方式进行是可能的,但我强烈建议不要这样做。这不仅是一个很大的工作,它将藐视所有阅读您的代码的人的期望 然而,对您的需求稍作调整就会产生一个简单的解决方案:编写
(x:val1 x)
,而不是(x:val1)
。现在,x
可以只是一个函数,它让它接收的输入决定它的行为:
user> (defn stat [distributions]
(fn [sample]
((get distributions sample))))
#'user/stat
user> (def s1 (stat {:val1 (constantly 1)
:val2 #(rand-int 5)}))
#'user/s1
user> (s1 :val1)
1
user> (s1 :val2)
3
user> (s1 :val2)
4
基本Clojure数据类型的基础是它们是纯的——它们是不可变的和无状态的。clojure记录或哈希映射没有“getter”(除了
get
,在同一映射或记录实例上使用相同的键调用时,它总是返回相同的值)。不确定的行为本质上是不纯粹的
也就是说,您可以将不纯过程存储在字段中,并调用该过程来获取您的值
user> (defrecord Stat [val1 val2])
user.Stat
user> (def s1 (Stat. 1 #(rand-nth [0 1 1 2 2 2 3 3 3 3 4 4 4 4 4])))
#'user/s1
user> ((:val2 s1))
1
user> ((:val2 s1))
1
user> ((:val2 s1))
3
user> ((:val2 s1))
4
user> ((:val2 s1))
4
user> ((:val2 s1))
4
user> ((:val2 s1))
3
如果您需要一个真正的非决定性的getter方法(如果调用方不能仅仅调用字段而不是访问字段),您可以使用
gen class
并定义一个getter方法。不能更改defrecord
定义的记录的查找行为deftype
提供了更多的控制,但是实现所有正确的接口需要一些工作。认识到这一点,您可以轻松定义具有自定义行为的类似地图的东西:
(use '[potemkin :only [def-map-type]])
(def-map-type Stat [val1 val2]
(get [_ k default-value]
(case k
:val1 val1
:val2 (val2)
default-value)))
(def s1 (->Stat 1 #(rand-int 10)))
(:val1 s1) ; => 1
(:val2 s1) ; => something in [0, 9]
(get s1 :val2) ; => something in [0, 9]
您还可以定义
assoc
、dissoc
和键,但它们对您的数据有意义。我可以使用协议来实现这一点:
(defprotocol Sample
(sample [m]))
然后,通过以下方式将协议扩展到要从中采样的任何结构:
- 映射、记录(以及任何其他关联数据类型):返回具有相同键的相同类型以及对每个值调用
示例
的结果
- 集合:从集合中随机选择一个元素
- 数字类型(java.lang.Number):返回未更改的值
- 函数类型(IFn):使用0个参数调用函数
- 任何其他内容(java.lang.Object):返回未更改的值(或错误,如果愿意…)
现在,您可以执行以下操作:
(sample [#{1 2} (partial rand-int 10) {:a 1 :b #{5 6}}])
=> [2 7 {:a 1 :b 6}]
这种方法的优点:
- 您可以为生成样本定义一个不可变的“模式”
- 创建示例后,它是一个纯不可变的Clojure数据结构(这很好,因为您不希望每次读取结果时都更改它!)
- 通过进一步扩展协议或创建新的采样函数,您可以在将来轻松地将其扩展到新类型的随机采样
- 很容易用高阶函数组合。例如,您可以执行
(获取1000个样本(重复地获取我的模式样本))
以获取1000个样本
如果您想更加详细,还可以将seed作为附加可选参数传递给sample
函数。如果操作正确,这将实现样本的再现性(这对于测试非常有用,并且它使(样本x种子)
作为一个纯函数工作)。是否有Java互操作要求需要使用defrecord?不,映射也可以。