Created
August 15, 2018 15:49
-
-
Save shilder/7b2412e76f40ba533166526a1132cd4e to your computer and use it in GitHub Desktop.
clojure fn caching and deftype issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns repl.pitfalls | |
(:require [clojure.core.async :as async]) | |
(:import (java.util.concurrent DelayQueue Delayed TimeUnit))) | |
; some interesting repl behaviour | |
; this are not bugs, but just some counterintuitive behaviour | |
; all of the described issues are caused by some state (thread/queue/defonce) | |
; fn-caching | |
(defn foo [a] | |
(println "Hello" a)) | |
(defonce async-thread | |
(async/thread | |
; foo is dereferenced and cached here, changing foo will not | |
; change calls of partial | |
(let [f (partial foo 1)] | |
(loop [] | |
; it will always print "Hello 1" | |
(f) | |
(Thread/sleep 2000) | |
(recur))))) | |
; little delay to prevent overlapping of output | |
(Thread/sleep 100) | |
(defonce async-thread2 | |
(async/thread | |
; there are 2 workarounds for this: | |
; 1. Use lambda and call required function - it will be dereferenced on every call | |
; 2. Use `var` special form (reader macro #') | |
(let [f1 #(foo 2) | |
; these 2 are the same | |
f2 (partial (var foo) 2) | |
f3 (partial #'foo 2)] | |
(loop [] | |
; This will change when foo is redefined | |
(f3) | |
(Thread/sleep 2000) | |
(recur))))) | |
(foo "Old") | |
; this definition will overwrite old `foo` for new calls, but in thread | |
; it still uses old version | |
(defn foo [a] | |
(println "Goodbye" a)) | |
(foo "New") | |
;; Deftype and storing objects | |
(defonce delay-queue (DelayQueue.)) | |
; every evaluation of deftype creates new class | |
(deftype MyNewDelay [endtime] | |
Delayed | |
(getDelay [this time-unit] | |
(.convert time-unit | |
(- endtime (System/currentTimeMillis)) | |
TimeUnit/MILLISECONDS)) | |
(compareTo | |
[this other] | |
(let [oend (.-endtime ^MyNewDelay other)] | |
(if (< endtime oend) | |
-1 | |
(if (= endtime oend) | |
0 | |
1))))) | |
(defonce object (->MyNewDelay 1)) | |
; on first load it will output true, on subsequent reloads it will output false | |
(println (= (class object) | |
(class (->MyNewDelay 1)))) | |
; You can't load this file twice it will cause CompilerException | |
; java.lang.ClassCastException: | |
; repl.pitfalls.MyNewDelay cannot be cast to repl.pitfalls.MyNewDelay | |
; | |
; this is because deftype generates new class every time it's evaluated | |
; and we have 'old' object in queue. So class names/bodyes are the same, but | |
; classes are not (from ClassLoader point of view) | |
(.put delay-queue (->MyNewDelay (+ (System/currentTimeMillis) 10000))) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment