简体   繁体   中英

How does clojure's defrecord method name resolution work?

After defining a record and the interfaces it implements, I can call its methods either by its name or using the java interop way using the dot operator.

 user=> (defprotocol Eat (eat [this]))
 Eat
 user=> (defrecord animal [name] Eat (eat [this] "eating"))
 user.animal
 user=> (eat (animal. "bob"))
 "eating"
 user=> (.eat (animal. "bob"))
 "eating"
 user=> 

Under the surface, what is going on there? Are there new clojure functions being defined? What happens when there are functions you defined that share the same name (is this possible?), how are these ambiguities resolved?

Also, is it possible to "import" java methods for other java objects so that you do not need the . operator so that behavior is like above? (For the purpose, for example, of unifying the user interface)

When you define a protocol, each of its methods are created as functions in your current namespaces. It follows that you can't have two protocols defining the same function in the same namespace. It also means that you can have them in separate namespaces and that a given type can extend both[1] of them without any nameclash because they are namespaced (in opposition to Java where a single class can't implement two interfaces with homonymous methods).

From a user perspective, protocol methods are no different from plain old non-polymorphic functions.

The fact that you can call a protocol method using interop is an implementation detail. The reason for that is that for each protocol, the Clojure compiler creates a corresponding backing interface. Later on when you define a new type with inline protocol extensions, then this type will implement these protocols' backing interfaces.

Consequently you can't use the interop form on an object for which the extension hasn't been provided inline:

(defrecord VacuumCleaner [brand model]
(extend-protocol Eat
  VacuumCleaner
  (eat [this] "eating legos and socks"))

(.eat (VaacumCleaner. "Dyson" "DC-20"))
; method not found exception

The compiler has special support for protocol functions so they are compiled as an instance check followed by a virtual method call, so when applicable (eat ...) will be as fast as (.eat ...) .

To reply to "can one import java methods", you can wrap them in regular fns:

(def callme #(.callme %1 %2 %3))

(obviously you may need to add other arities to account for overloads and type hints to remove reflection)

[1] however you can't extend both inline (at least one of them must be in a extend-* form), because of an implementation limitation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM