Search code examples
javagenericsclojuredispatchmultimethod

Clojure multimethod dispatch by java generics


Possible solution that I need:

How to implement a multimethod for a collection of type like Map<javaType, javaType>? Something like this:

(defmethod multimethod Map<javaType,javaType> [map]
  {(.key (first map)) (.value (first map))}) 

Whole explanation of my problem

Maybe the question above is not the solution I need to solve my problem (it's just I think that the implementation with generics can solve my problem), so I think I need to provide the whole explanation of what is my problem and ask community what I need to do.

I work with a java library in Clojure. Some functions return me java classes that I want to convert to clojure maps. I'm doing this with java.data library.

In most of cases this works fine. For some reason I need to implement java.data multimethod for several classes:

;; From java.data readme
(defmethod from-java YourJavaClass [instance]
  ; your custom logic for turing this instance into a clojure data structure)

But it's okay and this works fine:

(defmethod jd/from-java CurrencyPair [instance]
  (help/convert-market-keyword (.toString instance)))

But I encountered some class that can't be "mapped" for some reason.

(defmethod jd/from-java AccountInfo [instance]
  {:myWallet (jd/from-java (.getWallet instance))})

(defmethod jd/from-java Wallet [instance]
  {:myBalances (jd/from-java (.getBalances instance))})

(defmethod jd/from-java Balance [instance]
  "BALANCE!!!!") 

(defmethod jd/from-java Currency [instance]
  ;; e.g. converts Currency instance with field "BTC" to keyword :btc
  (help/convert-currency-keyword (.toString instance)))

After mapping an AccountInfo instance I expect to see this:

{:myWallet 
  {:myBalances 
    {:btc "BALANCE!!!!"
     :eth "BALANCE!!!!"
     :usdt "BALANCE!!!!"
     ...}

But see this:

{:myWallet 
  {:myBalances 
    {#object[org.knowm.xchange.currency.Currency 0x4faae851 "BTC"] 
     #object[org.knowm.xchange.dto.account.Balance 0x42942aa9 "Balance [currency=GNT, total=null, available=0E-8, frozen=0E-8, borrowed=0, loaned=0, withdrawing=0, depositing=0]"],

     #object[org.knowm.xchange.currency.Currency 0x299d00e0 "ETH"] 
     #object[org.knowm.xchange.dto.account.Balance 0x23f7cb1d "Balance [currency=LSK, total=null, available=0E-8, frozen=0E-8, borrowed=0, loaned=0, withdrawing=0, depositing=0]"],
     ...}

The returning value type of .getBalances() is Map<Currency,Balance> and it seems java.data don't know how to work with maps.

So, my question (at the moment) is how to implement the java.data multimethod for this kind of collection. Something like this:

(defmethod jd/from-java Map<Currency,Balance> [instance]
  {:cur "BALANCE!!!!"}) 

Just in case, the java library is XChange. The problem with Wallet class. Method getBalances().


Solution

  • As noted in the comments, using "generic types" at runtime is probably a non-starter. However, it appears that the java.data library does not call from-java recursively on keys and values in Map instances. The default implementation of from-java for Map is just (into {} instance). Perhaps the specific problem the OP is facing can be solved by redefining from-java method for java.util.Map. The new implementation would apply from-java to all keys and values. For example:

    (defmethod jd/from-java java.util.Map
      [m]
      (zipmap (map jd/from-java (keys m)) (map jd/from-java (vals m))))