Fork me on GitHub
#clojure
<
2021-06-20
>
mss00:06:33

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

seancorfield00:06:29

Sounds like you want eval

mss02:06:37

: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

eskos06:06:53

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

Rob Haisfield01:06:32

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

vemv06:06:52

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

👍 2
Jakub Šťastný01:06:40

@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!

2
Rob Haisfield04:06:18

Maybe part of it is the culture around DSLs?

👍 4
dgb2314:06:03

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.

4
👍 2
cdpjenkins17:06:51

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.

👍 4
kennytilton21:06:00

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?

anisoptera21:06:12

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.

anisoptera21:06:31

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.

anisoptera21:06:52

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

anisoptera21:06:49

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.

anisoptera21:06:49

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.

anisoptera21:06:03

Should I still use proxy, or is deftype workable?

anisoptera21:06:15

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

emccue21:06:17

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

anisoptera21:06:38

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

anisoptera21:06:27

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

anisoptera21:06:57

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

anisoptera21:06:27

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

anisoptera21:06:41

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

emccue21:06:11

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

emccue21:06:17

can you share some example json files

emccue21:06:29

and roughly what they map to in the generated java?

anisoptera21:06:07

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

anisoptera21:06:07

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

anisoptera21:06:10

(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)))))

anisoptera21:06:18

oh ignore the test fn at the end

anisoptera21:06:11

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

anisoptera21:06:24

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

anisoptera21:06:33

and ideally, even getting rid of the variadic part

anisoptera21:06:56

er not static, member

emccue21:06:11

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

anisoptera21:06:21

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

anisoptera21:06:31

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

emccue21:06:57

okay so assuming I had that abi contract loaded in

anisoptera21:06:06

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

anisoptera21:06:13

I want to be able to do that

anisoptera21:06:18

but also to have convenience

emccue21:06:33

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

anisoptera21:06:38

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

anisoptera21:06:41

it’s an ABI

emccue21:06:52

okay so what I mean is

anisoptera21:06:54

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

anisoptera21:06:28

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

anisoptera21:06:59

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

anisoptera21:06:39

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

anisoptera21:06:54

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

emccue21:06:07

(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)))

emccue21:06:12

okay treat that as pseudocode

emccue21:06:22

you could now do

emccue21:06:34

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

anisoptera22:06:09

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

emccue22:06:18

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

anisoptera22:06:18

with an arg map even

emccue22:06:29

like my code above doesn't do that

anisoptera22:06:34

right i get what you’re going for

emccue22:06:34

but thats where i was going with it

anisoptera22:06:37

we’re just .. yeah

emccue22:06:34

just make sure to insert all the warnings you have to

emccue22:06:10

and potentially validate/restructure your contracts in memory

emccue22:06:28

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

emccue22:06:38

and might store these all keyed under the method names

emccue22:06:50

to avoid the linear scan or wtvr

anisoptera22:06:00

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

anisoptera22:06:10

and yeah needs caching or w/e

emccue22:06:27

but crypto burns a rainforest per call anyways so who cares

andy.fingerhut22:06:43

Wow, whatever happened to understatement? 🙂

anisoptera22:06:29

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

anisoptera22:06:33

lol read calls are super cheap

anisoptera22:06:43

it’s the write calls that cost a bunch

anisoptera22:06:05

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

anisoptera22:06:23

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

emccue22:06:43

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

emccue22:06:08

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

emccue22:06:17

or a map with namespaced keys that hold the stateful bits

emccue22:06:25

(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) ...))

❤️ 2
emccue22:06:42

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

anisoptera22:06:54

ah that makes sense

anisoptera22:06:15

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

anisoptera22:06:23

which is usually what i did

emccue22:06:44

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

emccue22:06:58

personal preference

anisoptera22:06:11

yep makes sense. thanks a ton 🙂