使用概率字段统一访问Clojure记录

时间:2014-02-04 19:34:07

标签: clojure records

是否有一种优雅的方式来创建和使用具有常量和概率字段的记录。我希望能够做类似下面的事情,其中​​“sampler”是一个从某个分布返回样本的函数。目标是使用户对访问的文件是持续的还是概率性的是透明的。

> (defrecord Stat [val1 val2])

> (def s1 (Stat. 1 sampler))

> (:val1 s1)

> 1

> (:val2 s1)

> 4

> (:val2 s1)

> 2

4 个答案:

答案 0 :(得分:8)

对于自定义deftypes,关键字查找的行为可能不仅仅是在地图中查找固定值,但我强烈建议不要使用它。它不仅需要做很多工作,还会违背每个人阅读代码的期望。

然而,稍微调整一下您的要求会产生一个简单的解决方案:代替(: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

答案 1 :(得分:6)

defrecord定义的记录的查找行为无法更改。 deftype提供更多控制,但实现所有正确的接口需要一些工作。意识到,potemkin可让您轻松定义具有自定义行为的类似地图的内容:

(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]

您还可以定义assocdissockeys,但这些数据对您的数据有意义。

答案 2 :(得分:2)

我会通过使用协议来做到这一点:

(defprotocol Sample
  (sample [m]))

然后通过以下方式将协议扩展到您想要采样的任何结构:

  • 地图,记录(以及任何其他类型的数据类型):使用相同的键返回相同的类型,并在每个值上调用sample的结果
  • 设置:从随机设置中选择一个元素
  • 数字类型(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数据结构(这很好,因为你不希望每次读取结果都改变它!)
  • 通过进一步扩展协议或创建新的采样功能,您可以轻松将其扩展到新类型的随机抽样
  • 使用高阶函数很容易编写。例如,您可以(take 1000 (repeatedly #(sample my-schema)))获取1000个样本。

如果您想要更详细,您还可以将种子作为附加可选参数传递给sample函数。如果您正确地执行此操作,这将实现样本的可重复性(这对于测试非常有用,并且它使(sample x seed)作为纯函数工作)。

答案 3 :(得分:1)

基本的Clojure数据类型的基础是它们是纯粹的 - 它们是不可变的和无状态的。对于clojure Record或hash-map(除了get之外,没有“getter”这样的东西,当在同一个map或记录实例上使用相同的键调用时,它总是会返回相同的值)。不确定的行为本质上并不纯粹。

也就是说,你可以将不纯的程序存储在一个字段中,并调用该程序来获取你的价值。

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方法。