clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
2021-06-20T00:46:33.218600Z

have a bit of a weird question. let’s say I have a string of some clojure code like "#(println %)" or "(fn [& args] (println args))" that I want to apply some arguments to at runtime. how do I go about converting that string representation of the fn to something invokable? calling read-string results in a cons/list, and I’m a little bit lost in the weeds trying to create something out of that string that implements IFn and is invokable

seancorfield 2021-06-20T00:54:29.219Z

Sounds like you want eval

2021-06-20T01:29:32.220100Z

Why is there so much crossover between Ruby and Clojure programmers?

2021-06-20T01:41:40.226800Z

@rob370 is there? Well, if that's the case, I'd very much like to know as well. I am (was) a Ruby dev, to me I wanted to work in a lisp (simplicity of syntax, macros) as well as with a functional language (immutability, not OOP-centered). Ruby uses FP style a lot, I don't think I've ever seen a for loop, everyone uses each/map/reduce etc, Ruby has symbols as well, the :key syntax is familiar etc, so I'd say the barrier of entry is lower than coming from say C# or even Python. Would love to hear other people's thoughts on this one!

➕ 1
2021-06-20T02:02:37.226900Z

:face_palm: I dunno why I was overthinking stuff I read on page one of sicp but thanks for rubber ducking with me. appreciate the response

2021-06-20T04:11:18.228100Z

Maybe part of it is the culture around DSLs?

👍 2
vemv 2021-06-20T06:46:52.232600Z

I like to think we're a rebellious and creative bunch!

👍 1
eskos 2021-06-20T06:47:53.232800Z

You may also want to check out https://github.com/borkdude/sci

dgb23 2021-06-20T14:31:03.243700Z

I don’t know Ruby much, but I think both languages are designed and advertised around a certain programmer mindset. Highly dynamic, expressive and respecting of the programmer. Contrast it to say Go, which is designed in a way so a two week beginner (that is already trained in programming otherwise) can pretty much jump into a piece of code and understand it. Very few, obvious and clean concepts that are a little bit like a refined version of the lowest common denominator of mainstream languages.

👍 1
➕ 2
2021-06-20T17:07:51.253200Z

Ruby and Clojure both came to prominence in the 00s (I appreciate Ruby was around before then but it really took off when Rails came along), at a time when I dare say most developers were using languages that didn’t support creating powerful abstractions. I certainly was. I remember reading various Paul Graham essays around about the time, especially Beating The Averages, and hearing that there were more powerful languages out there, languages that had features that a Java programmer like me couldn’t even conceive of, that would make me much more productive than I could imagine. I ended up learning Common Lisp, then Python (sadly I never learned much Ruby at the time) and finally Clojure. I concede that Mr Graham had a point. Funnily enough, he didn’t mention Ruby but I put that language in the same category. Things like metaprogramming and functional programming are amazing and it’s easy to forget what life was like before languages like Ruby and Clojure became well known, and both languages allow you to do things that simply couldn’t be done with the languages that were popular back then.

👍 2
2021-06-20T21:33:12.256700Z

I’m building a Clojure wrapper for web3j (blockchain crap) and basically I’m trying to take a JSON description of a contract’s interface and turn it into a bunch of convenience methods for calling those functions. web3j does this with an external codegen tool that generates class definitions for the methods. Of course my first thought was to use macros to do basically the same thing. However, having not done this level of work in Clojure before, I’m struggling with the proper way to implement it.

2021-06-20T21:35:31.259200Z

If I want to mirror the way web3j generates its classes, my current thinking is I’d make a macro that defprotocols the contract’s interface, and another that proxys a subclass of the web3j Contract class + implements my generated protocol. That seems like the best approach, and gets me basically the same result as if I ran web3j’s codegen.

2021-06-20T21:35:52.259500Z

(the Contract class is an abstract so I would have to use proxy)

2021-06-20T21:37:49.261200Z

Just trying to decide if this actually results in a more ergonomic experience than something else that defines some map of free functions based on the interface, then calling those against proxies. Of course, the Contract constructor is meant to result in a convenient experience from Java, so you inject a bunch of other dependencies too.

2021-06-20T21:38:49.262300Z

so I think I’m sort of talking myself into using the protocol based mechanism, because it’d only create one instance of the contract per, well, instance of the contract.

2021-06-20T21:39:03.262600Z

Should I still use proxy, or is deftype workable?

2021-06-20T21:41:15.263100Z

kind of looking like since it’s an abstract base class I have to use proxy or reimplement everything the abstract base does.

emccue 2021-06-20T21:46:17.263900Z

@anisoptera kindof a dumb question, but what is wrong with the Java lib?

2021-06-20T21:46:38.264300Z

I don’t want to have to generate the class definitions ahead of time

2021-06-20T21:47:27.265300Z

I want a library that works like ethersjs, which for context allows one to say a partial interface definition (i.e. one function) and then be able to call that function right away in that session

2021-06-20T21:47:57.265700Z

There’s a lot the Java lib does right and that’s why I’m not reimplementing it

2021-06-20T21:48:27.266400Z

I am just fixing this one thing so I can use it conveniently from Clojure without having to generate .class files ahead of time

2021-06-20T21:48:41.266800Z

(I mean, the other problem is that it generates Java code, not Clojure code)

kennytilton 2021-06-20T21:49:00.267200Z

Ruby? You mean MatzLisp? Yep, it has a place. In, I believe, Mr Hickey's 10th anniversary ClojureCon talk, he called out Smalltalk and Common Lisp as his inspirations...maybe he was on the trail already when Rails popped Ruby onto everyone's radar?

emccue 2021-06-20T21:49:11.267800Z

i mean, I would personally bite the build system bullet and do it AOT - but

emccue 2021-06-20T21:49:17.268200Z

can you share some example json files

emccue 2021-06-20T21:49:29.268500Z

and roughly what they map to in the generated java?

2021-06-20T21:50:07.269500Z

the java generation is mainly to generate function signatures that take the proper input params / give the right outputs

emccue 2021-06-20T21:51:02.270300Z

okay

2021-06-20T21:51:07.270500Z

so like here is kind of an example of what I did in clojure to handle single value returning view functions

2021-06-20T21:51:10.270800Z

(def contract
  (proxy [org.web3j.tx.Contract]
      [org.web3j.tx.Contract/BIN_NOT_PROVIDED       ;; contractBinary
       "0xB44825cF0d8D4dD552f2434056c41582415AaAa1" ;; contractAddress
       web3j ;; web3j
       credentials ;; credentials
       gasProvider ;; gasProvider
       ]
    (test [b]
      (str b))))

(defn make-read-contract-call-single-return [name input-types output-types]
  (fn [contract & args]
    (let [function (org.web3j.abi.FunctionEncoder/makeFunction
                    name input-types args output-types)]
      (.getValue (.executeCallSingleValueReturn contract function)))))

2021-06-20T21:51:18.271Z

oh ignore the test fn at the end

2021-06-20T21:51:22.271200Z

obvi 🙂

2021-06-20T21:52:11.272100Z

so the compiler would have generated a subclass of org.web3j.tx.Contract and would have generated a static function which essentially does what the returned fn from make-read-contract-call-single-return does

2021-06-20T21:52:24.272500Z

the next step is generating a bunch of calls to that, and binding them to names

2021-06-20T21:52:33.272900Z

and ideally, even getting rid of the variadic part

2021-06-20T21:52:56.273500Z

er not static, member

emccue 2021-06-20T21:53:11.273800Z

dumb solution, might not be what you want, but you can always make a "invoke-contract-function"

2021-06-20T21:53:21.274Z

yeah, that’s where I’m at right now

2021-06-20T21:53:31.274500Z

I have that, my next step is generating convenience methods basically

emccue 2021-06-20T21:53:57.275100Z

okay so assuming I had that abi contract loaded in

2021-06-20T21:54:06.275400Z

It’s kind of messy to have to define the list of input types and output types every time you want to call anything

2021-06-20T21:54:13.275600Z

I want to be able to do that

2021-06-20T21:54:18.275800Z

but also to have convenience

emccue 2021-06-20T21:54:33.276200Z

wait, why do you have do define your input and output types?

2021-06-20T21:54:38.276500Z

(defn balanceOf [contract addr]
  ((make-read-contract-call "balanceOf" ["address"] ["uint256"]) contract addr))

2021-06-20T21:54:41.276700Z

it’s an ABI

emccue 2021-06-20T21:54:52.277100Z

okay so what I mean is

2021-06-20T21:54:54.277300Z

so the system has to know what input/output types are being encoded and decoded into the call data

2021-06-20T21:55:28.278400Z

this balanceOf function is an example of what I would have to write for every unique function call I wanted to make

2021-06-20T21:55:59.279200Z

so that in the end i can write (balanceOf contract "0x00000…")

2021-06-20T21:56:39.280300Z

I’m already there, with manual entry of the final convenience function, but I don’t wanna do this manually when I could just generate

2021-06-20T21:58:54.282600Z

basically I guess I’m just looking for what the most clojure-y way would be to have a convenient interface to this stuff, if we ignored that web3j was underpinning it

emccue 2021-06-20T21:59:07.282900Z

(def ex-contract
  [{
        "constant": true,
        "inputs": [],
        "name": "totalSupply",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    }])

(defn invoke-read
  [contract name input]
  (loop [methods contract]
    (if (= (get (first methods) "name") name)
      ((make-read-contract-all name 
                               (mapv #(get % "type")
                                     (get (first methods) "input"))
                               (mapv #(get % "type")
                                     (get (first methods) "output")) input)))

emccue 2021-06-20T21:59:12.283200Z

okay treat that as pseudocode

2021-06-20T21:59:20.283500Z

ohhhhh

2021-06-20T21:59:21.283700Z

interesting

emccue 2021-06-20T21:59:22.283800Z

you could now do

emccue 2021-06-20T21:59:34.284200Z

(invoke-read ex-contract "totalSupply" {:_from ... :_to ... :_value ...})

2021-06-20T22:00:09.284700Z

I mean we still need an address and everything but I like this yeah

2021-06-20T22:00:11.284900Z

ohhhh

emccue 2021-06-20T22:00:18.285300Z

and then you would expect a map out with the return values

2021-06-20T22:00:18.285400Z

with an arg map even

emccue 2021-06-20T22:00:29.285600Z

like my code above doesn't do that

2021-06-20T22:00:34.286Z

right i get what you’re going for

emccue 2021-06-20T22:00:34.286200Z

but thats where i was going with it

2021-06-20T22:00:37.286400Z

we’re just .. yeah

2021-06-20T22:00:40.286600Z

I like it

emccue 2021-06-20T22:01:34.287500Z

just make sure to insert all the warnings you have to

emccue 2021-06-20T22:02:10.288Z

and potentially validate/restructure your contracts in memory

emccue 2021-06-20T22:02:28.288500Z

like maybe have a namespace that de-facto defines your "contract" type

emccue 2021-06-20T22:02:38.289Z

and might store these all keyed under the method names

emccue 2021-06-20T22:02:50.289500Z

to avoid the linear scan or wtvr

2021-06-20T22:03:00.290100Z

see this is genius, here i am thinking i have to mangle the data into some format that’s acceptable and you’re like no, just use the abi as your interface directly

2021-06-20T22:03:10.290300Z

and yeah needs caching or w/e

emccue 2021-06-20T22:03:27.290800Z

but crypto burns a rainforest per call anyways so who cares

2021-06-20T22:03:29.291Z

but it feels a lot better than what i was doing which felt like writing java with parentheses

2021-06-20T22:03:33.291200Z

lol read calls are super cheap

2021-06-20T22:03:43.291500Z

it’s the write calls that cost a bunch

2021-06-20T22:04:05.292200Z

and i’m on a network that doesn’t boil the oceans, but yes, in general we’re not worried about high perf on a lot of these calls

2021-06-20T22:04:23.292500Z

and if i become concerned with perf, i can revisit the build step

emccue 2021-06-20T22:05:43.293100Z

yeah - and you might need to attach your web3j client to it in some way

emccue 2021-06-20T22:06:08.293600Z

the technique i've used for stuff like that is to return either a named map with defrecord

emccue 2021-06-20T22:06:17.293900Z

or a map with namespaced keys that hold the stateful bits

emccue 2021-06-20T22:07:25.295400Z

(defn create-contract [web3j-client contract-spec]
  (validate! contract-spec)
  {::web3j-client web3j-client
   ::contract-spec contract-spec})

(defn do-thing [contract]
  (.someMethod (::web3j-client contract) ...))

❤️ 1
emccue 2021-06-20T22:07:42.295900Z

absent other context you can use namespaced keys as a kind of private

2021-06-20T22:07:54.296200Z

ah that makes sense

2021-06-20T22:08:15.296700Z

not using *web3j-client* or something silly like that

2021-06-20T22:08:23.296900Z

which is usually what i did

emccue 2021-06-20T22:08:44.297300Z

that works too, i just like this better when i'm writing from scratch

emccue 2021-06-20T22:08:58.297500Z

personal preference

2021-06-20T22:09:11.297700Z

yep makes sense. thanks a ton 🙂

2021-06-20T22:53:43.297800Z

Wow, whatever happened to understatement? 🙂