Fork me on GitHub
#beginners
<
2022-05-06
>
jlmr08:05:36

So I’m in over my head. I’m messing around with deftype , trying to create a custom vector-like type. Now when I try to create a new empty CustomVector I’m getting the following error:

Error printing return value (AbstractMethodError) at base.CustomVector/equiv (NO_SOURCE_FILE:-1).
Method base/CustomVector.equiv(Ljava/lang/Object;)Z is abstract
And I have no idea what this means and how I can fix this

p-himik08:05:54

It would be helpful to have the source code.

jlmr08:05:55

Ok the actual thing is called an ObliqueVector:

(deftype ObliqueVector [state mutable mta]

  Oblique
  (id [_this] (:id state))

  (holds [_this] (:holds state))

  ;; Replace with better function to fork, see next TODO
  (update-replica-id [_this new-replica-id]
    (ObliqueVector. (assoc state :replica-id new-replica-id)
                    mutable
                    mta))

  ;; TODO: Forking should recursively update all nested oblique collections so that replica-id
  ;; and stuff like the queue and tx-idx counter reference the same things again.
  (fork [_this]
    (if (:root? state)
      (let [new-replica-id (random-id)]
        (ObliqueVector. (-> state
                            (assoc :replica-id new-replica-id)
                            (update :tree walk/postwalk (fn [form] (if (oblique? form)
                                                                     (update-replica-id form new-replica-id)
                                                                     form))))
                        (make-mutable)
                        {}))
      (throw (ex-info "Cannot fork non-root Oblique Collection" {}))))

  ;; TODO: implement
  (merge-op [this _op] this)

  ;; TODO: implement
  (merge-op [this _op _data] this)

  clojure.lang.IPersistentVector
  (assocN [this i val]
    (let [origin (get (tree/left-of-pos (:tree state) i) :id)
          block-id (next-block-id! state mutable)]

      ;; Check if index is in range
      ;; TODO: move this higher, we are already performing side-effects in next-block-id!
      (if (<= (int 0) i (count this))
        (cond
          ;; The value is a regular collection
          ;; We transform it to an Oblique Collection
          (regular-coll? val)
          (let [coll-id (random-id)
                new-coll (coll->oblique coll-id mutable val)
                block {:id block-id
                       :origin origin
                       :content new-coll}]
            (add-to-queue! (:queue mutable) block)
            (ObliqueVector. (cond-> state
                              true (update :holds conj coll-id)
                              true (update :tree tree/assoc-pos i block)
                              (:root? state) (assoc :clock (.get (:clock mutable)))
                              (:root? state) (update :yarns feed-yarns! (:queue mutable)))
                            mutable
                            mta))

          ;; We are associng a value that already is a an Oblique Collection
          (oblique-coll? val)
          (if (and (contains? (:holds state) (id val))
                   (= (id val) (id (get this i))))
            ;; This vector already holds this oblique collection at this position
            (let [block {:id block-id
                         :origin origin
                         :content val}]
              (add-to-queue! (:queue mutable) block)
              (ObliqueVector. (cond-> state
                                true (update :holds set/union (:holds val))
                                true (update :tree tree/assoc-pos i block)
                                (:root? state) (assoc :clock (.get (:clock mutable)))
                                (:root? state) (update :yarns feed-yarns! (:queue mutable)))
                              mutable
                              mta))
            ;; TODO For now this is undefined behaviour, but we could improve this
            (throw (ex-info "Can't assoc coll that is already Oblique at new location" {})))

          ;; We are dealing with a scalar
          ;; TODO Might want to check for non-scalar oblique things (ObliqueString?) as well
          :else
          (let [block {:id block-id
                       :origin origin
                       :content val}]
            (add-to-queue! (:queue mutable) block)
            (ObliqueVector. (cond-> state
                              true (update :tree tree/assoc-pos i block)
                              (:root? state) (assoc :clock (.get (:clock mutable)))
                              (:root? state) (update :yarns feed-yarns! (:queue mutable)))
                            mutable
                            mta)))
        (throw (IndexOutOfBoundsException.)))))

  (length [this]
    (.count this))

  (cons [this o]
    (.assocN this (count this) o))

  clojure.lang.Counted
  (count [_this] (get-in state [:tree :pos]))

  clojure.lang.Associative
  (assoc [this k v]
    (if (integer? k)
      (.assocN this k v)
      (throw (IllegalArgumentException. "Key must be integer"))))

  (containsKey [this k]
    (and (integer? k)
         (<= (int 0) (int k))
         (< (int k) (.count this))))

  (entryAt [this k]
    (when (.containsKey this k)
      (clojure.lang.MapEntry. k (:content (.nth this (int k))))))

  clojure.lang.Indexed
  (nth [this i]
    (if (and (<= (int 0) i) (< i (count this)))
      (:content (tree/get-pos (:tree state) i))
      (throw (IndexOutOfBoundsException.))))

  (nth [this i not-found]
    (if (and (<= (int 0) i) (< i (count this)))
      (.nth this i)
      not-found))

  ;; TODO: possible bug here: what if k is nil?
  ;; Should first if k exists?
  clojure.lang.ILookup
  (valAt [_this k not-found]
    (let [result (tree/get-pos (:tree state) k)]
      (if result
        (:content result)
        not-found)))

  (valAt [_this k]
    (:content (tree/get-pos (:tree state) k)))

  clojure.lang.Seqable
  (seq [this]
    (if (zero? (.count this))
      nil
      (map :content (tree/only-content (:tree state))))))

jlmr08:05:06

there are still a lot of todo’s and stuff in there to fix

p-himik08:05:53

clojure.lang.IPersistentVector extends many other interfaces - you have to implement all those methods as well. Including the equiv one that the error mentions.

jlmr08:05:19

ah ok, is there a list somewhere with which interfaces to implement?

p-himik08:05:49

Dunno - I just read the .java source code of Clojure. :) But you could probably use Java reflection to find all the methods, albeit that's clunky.

jlmr08:05:59

Ok thanks, will look into it!

👍 1
Mark Wardle14:05:00

I've heard good things about potemkin and creating custom types - e.g. maps - but I think there is a posh deftype+ which might help? https://github.com/clj-commons/potemkin

Mark Wardle14:05:15

But no personal experience of using it sorry

nottmey12:05:25

Hey there, building my first Clojure library, and it aims to combine Datomic and Lacinia! 😳 Still in draft and all, but happy to read your thoughts! (And also keen to get coding feedback 🙊) https://github.com/nottmey/datomic-lacinia

🙌 1
Benjamin13:05:34

Issue: Use clojure as part of some cli tool Initial startup time is ok, but I have to start it multiple times idea 1: Start clojure program once and send multiple commands (socket repl? nrepl? read loop? ) idea 2: make the rest of the cli batch up the requests idea 3: use bb

babashka 3
Jo Øivind Gjernes16:05:48

4. Build with graalvm

didibus17:05:45

If it's a CLI, option 4 is the best, use GraalVM

didibus17:05:08

That will give you fast startup, fast runtime, single binary release with no dependencies.

Benjamin17:05:27

yea nbb is another option

didibus17:05:00

I'd say it depends on your end user. If you don't want them having to install Node, NPM and nbb or having to install bb, then GraalVM is best. If you think they'll already have Node and NPM, nbb might work better. If you think they'd already have bb, that's a good choice then. In terms of performance: 1. GraalVM - fastest startup, fastest runtime, lowest memory 2. bb - fast startup, slow runtime, low memory 3. nbb - slowest (but still fast) startup, slowest (but still good) runtime, highest (but still reasonable) memory

👍 1
sheluchin18:05:59

Are taps used in production as well?

R.A. Porter18:05:17

Some people/projects feel strongly about removing them, but they're basically a noop if there are no tap targets assigned. I prefer keeping the useful ones around, for connecting to the repl and remote debugging (without having to update the code on the fly), but most of the projects I've worked on have preferred they be removed, so I do.

R.A. Porter18:05:35

If you think of them like log statements at debug level - which normally wouldn't be turned on in production, but could be if needed - then you'd be seeing them the way I do.

sheluchin13:05:35

Thanks @U01GXCWSRMW, it makes sense.

mbjarland20:05:18

umm…so in a situation where I’m tempted to do (apply or (map f coll)) to return the first non nil item after map, is there something more idiomatic than (first (keep f coll)) ?

hiredman20:05:24

(some identity ...)

hiredman20:05:44

or in your case (some f ...)

Jose Varela23:05:53

A bit unrelated but didn’t know where to post this: Many of us don’t have jobs where Clojure is used. To address this, one thing I have been trying to do is port some lessons from Clojure to my stack (DOP, REPL, FP, etc). Have you seen examples in JS/TS-land using React/ReactNative that mimic Clojure’s REPL workflow? I’ve been extracting code from React components to make it easier to play with logic like I do in Calva. My hunch is that the ecosystem does not prioritize this because devs are used to either opening the app and using the browser’s console or writing unit tests. Anyway, would love to hear your thoughts.

bringe01:05:32

I don’t think the languages and tooling are as capable of having a repl workflow as extensive as something like Clojure’s, and I haven’t seen any similar workflow in JS/TS land.

Jose Varela17:05:37

You’d be surprised! This week I setup a repl with hot reloading using node’s repl module. Currently looking for a data browser ala Portal.

Jose Varela17:05:52

I think it’s cultural, most people don’t even know this is a more enjoyable way of programming.

Jose Varela17:05:50

Showed this to a few teammates and they are mindblown, hadn’t even ocurred to them this could be a thing.

bringe02:05:17

> This week I setup a repl with hot reloading using node’s repl module. Currently looking for a data browser ala Portal. Are you talking about a repl workflow with JS/TS or with Clojure? Hot reload with JS/TS is one thing, but being able to evaluate any arbitrary piece of code straight from the editor without needing to compile all code, and see the results immediately, and then build up a live program (which we can do in Clojure), is a different thing. I may misunderstand what you’re saying though. 😃

Jose Varela03:05:40

Yup, REPL workflow with TS/JS. Similar to Calva, I open up a ts-node console (instead of nrepl) and press alt+enter to eval inside my editor. Took precautions to make things ‘reloadable’ (using let instead of const; created different reload functions, etc) and added keybindings for that. By hot reloading in this context, I meant that if I save a file, the dependency is reloaded in my REPL with the new code. This is all duct taped but it’s possible. That’s why I said the problem isn’t technical but cultural: this takes time/effort and if you haven’t seen the benefits it’s hard to argue for doing it.

bringe16:05:53

Interesting. I think I’d need to see it in action to understand it better. > I open up a ts-node console (instead of nrepl) and press alt+enter to eval inside my editor So you are eval’ing from a console/prompt, like a browser console, just for example, or are you eval’ing from your source code files themselves?

Jose Varela16:05:31

Source code files to console in terminal tab, I’m not using the browser console.

Jose Varela16:05:07

It’s a toy lol nothing comparable to what we have in clojure with repls

bringe02:05:59

> I’m not using the browser console I just mentioned a browser console as an example to clarify what I was asking 😃. Anyway, sounds pretty neat. Do you have a short gif / clip of it in action?