9

我使用defrecord字段的类型提示创建了一个类型。然而,我发现这些类型提示并没有在构造函数中强制执行,我可以用它们做一些奇怪的事情。例如,请看下面的代码片段:

user=> (defrecord Person [#^String name #^Integer age])
user.Person
user=> (seq (.getConstructors Person))
(#<Constructor public user.Person(java.lang.Object,java.lang.Object,
java.lang.Object,java.lang.Object)>
#<Constructor public user.Person(java.lang.Object,java.lang.Object)>)
user=> (Person. (Integer. 123) "abhinav")
#:user.Person{:name 123, :age "abhinav"}

显示的构造函数签名与提供的类型提示不匹配(它们Object同时用于StringInteger),并且我能够构造具有错误字段类型的对象。

我的代码有问题还是 Clojure 中的错误?

我在 Clojure 1.2.0-beta1 上。

4

2 回答 2

9

类型提示用于避免反射;它们不(当前)用于静态类型函数或构造函数参数(例外是原语,因为它们不能包含在 中Object)。因此,它们对于简单的记录并没有做太多的事情,但是在添加协议实现时它们确实很重要,例如:

用户=>(设置!*warn-on-reflection* true)
真的
用户=> (defprotocol P (foo [p]))
磷
user=> (defrecord R [s] P (foo [_] (.getBytes s))) ; getBytes 是 String 上的一个方法
反射警告,NO_SOURCE_PATH:6 - 无法解析对字段 getBytes 的引用。
用户.R
用户=> (foo (R. 5))
java.lang.IllegalArgumentException:未找到匹配字段:类 java.lang.Integer 的 getBytes (NO_SOURCE_FILE:0)
用户=> (defrecord R [^String s] P (foo [_] (.getBytes s)))
用户.R
用户=> (foo (R. 5))
java.lang.ClassCastException:java.lang.Integer 无法转换为 java.lang.String (NO_SOURCE_FILE:0)

两个版本之间的区别在于后者发出字节码调用String.getBytecode()(因此在传递 Integer 时会出现 ClassCastException),而前者需要查找.getBytes相对于传递给函数的运行时对象的确切含义(并且该过程在传递时失败一个整数)。

于 2010-07-27T18:24:07.190 回答
6

据我所知,当前仅在涉及原始类型时才强制执行类型提示deftype和字段:defprotocol

(deftype Foo [^int x])

(Foo. 5)    ; => OK
(Foo. :foo) ; => no go

;; ... and likewise with defprotocol

我对这是一个公认的问题有一个非常模糊的回忆,尽管我不确定计划是记录这种行为还是强制执行非原始提示......我会试着找出答案。

于 2010-07-27T16:18:03.247 回答