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
Sounds like you want eval
…
Why is there so much crossover between Ruby and Clojure programmers?
@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!
: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
Maybe part of it is the culture around DSLs?
I like to think we're a rebellious and creative bunch!
You may also want to check out https://github.com/borkdude/sci
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.
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.
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.
If I want to mirror the way web3j generates its classes, my current thinking is I’d make a macro that defprotocol
s the contract’s interface, and another that proxy
s 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.
(the Contract class is an abstract so I would have to use proxy)
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.
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.
Should I still use proxy, or is deftype workable?
kind of looking like since it’s an abstract base class I have to use proxy or reimplement everything the abstract base does.
@anisoptera kindof a dumb question, but what is wrong with the Java lib?
I don’t want to have to generate the class definitions ahead of time
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
There’s a lot the Java lib does right and that’s why I’m not reimplementing it
I am just fixing this one thing so I can use it conveniently from Clojure without having to generate .class files ahead of time
(I mean, the other problem is that it generates Java code, not Clojure code)
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?
i mean, I would personally bite the build system bullet and do it AOT - but
can you share some example json files
and roughly what they map to in the generated java?
the java generation is mainly to generate function signatures that take the proper input params / give the right outputs
okay
so like here is kind of an example of what I did in clojure to handle single value returning view functions
(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)))))
oh ignore the test fn at the end
obvi 🙂
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
the next step is generating a bunch of calls to that, and binding them to names
and ideally, even getting rid of the variadic part
er not static, member
dumb solution, might not be what you want, but you can always make a "invoke-contract-function"
yeah, that’s where I’m at right now
I have that, my next step is generating convenience methods basically
okay so assuming I had that abi contract loaded in
It’s kind of messy to have to define the list of input types and output types every time you want to call anything
I want to be able to do that
but also to have convenience
wait, why do you have do define your input and output types?
(defn balanceOf [contract addr]
((make-read-contract-call "balanceOf" ["address"] ["uint256"]) contract addr))
it’s an ABI
okay so what I mean is
so the system has to know what input/output types are being encoded and decoded into the call data
this balanceOf function is an example of what I would have to write for every unique function call I wanted to make
so that in the end i can write (balanceOf contract "0x00000…")
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
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
(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)))
okay treat that as pseudocode
ohhhhh
interesting
you could now do
(invoke-read ex-contract "totalSupply" {:_from ... :_to ... :_value ...})
I mean we still need an address and everything but I like this yeah
ohhhh
and then you would expect a map out with the return values
with an arg map even
like my code above doesn't do that
right i get what you’re going for
but thats where i was going with it
we’re just .. yeah
I like it
just make sure to insert all the warnings you have to
and potentially validate/restructure your contracts in memory
like maybe have a namespace that de-facto defines your "contract" type
and might store these all keyed under the method names
to avoid the linear scan or wtvr
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
and yeah needs caching or w/e
but crypto burns a rainforest per call anyways so who cares
but it feels a lot better than what i was doing which felt like writing java with parentheses
lol read calls are super cheap
it’s the write calls that cost a bunch
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
and if i become concerned with perf, i can revisit the build step
yeah - and you might need to attach your web3j client to it in some way
the technique i've used for stuff like that is to return either a named map with defrecord
or a map with namespaced keys that hold the stateful bits
(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) ...))
absent other context you can use namespaced keys as a kind of private
ah that makes sense
not using *web3j-client*
or something silly like that
which is usually what i did
that works too, i just like this better when i'm writing from scratch
personal preference
yep makes sense. thanks a ton 🙂
Wow, whatever happened to understatement? 🙂