Fork me on GitHub
#beginners
<
2017-05-05
>
rishat10:05:47

I asked this question in the past but maybe things have changed, so I’ll ask again: Is it possible to add a dependency to a project via CLI, in a way similar to how you do it with pip in Python, gem in Ruby, npm in Node, etc. Be it lein or boot project, doesn’t matter. Maintaining precise versions and having to go to Clojars to get the actual version of a lib feels unreasonable to me.

noisesmith16:05:03

rishat: to me, having a deploy break because a library was changed since the last deploy seems unreasonable

mobileink19:05:30

in boot you can use -d if memory serves.

sdaas10:05:08

Going back to question asked by @jeff.engebretsen …. Doesn’t the following work ? Or am I missing something ?

$ (def x [{:a 1} {:a 2} {:b 1} {:b 2}])
$ (vals (group-by vals x))
=> ([{:a 1} {:b 1}] [{:a 2} {:b 2}])

john11:05:01

@rishat Not the same, but there's lein ancient

john11:05:31

which just let's you know which deps are old

matan17:05:44

curlyfry: I consider myself lazy, yet even I wouldn't use this hack 🙂 so many other routine things I'd address first 🙂 anyway cute 🙂

jeff.engebretsen15:05:08

I ended up changing things up. I added animations and need atoms around single numbers instead of just re rendering everything. So, I don't need the groupings anymore.

matan17:05:46

I am having a hard time every time I have to translate from the repl-oriented docs of namespace manipulation forms, to the accepted syntax to use inside ns forms and inside require forms inside ns forms ― within source files.

matan17:05:23

And specifically I would like to do something like:

(:require [honeysql.helpers :refer :rename [update sql-update]])

matan17:05:30

Any advice on both accounts?

mtkp17:05:04

docs tend to (require ... :refer :all) or (use ...) for convenience but in practice i pretty much always alias, especially 3rd party libs.

matan18:05:58

alexmiller: well, writings like > Do not :refer :all don't really help with understanding...

Alex Miller (Clojure team)18:05:42

:refer :all means that you have added many names to your namespace with no indication of where they came from

Alex Miller (Clojure team)18:05:14

I strongly prefer to alias a required namespace and use foo/the-thing instead

matan18:05:19

Yes makes sense

matan18:05:48

Unfortunately some libraries kind of suggest the wrong way, case in point https://github.com/jkk/honeysql#usage

Alex Miller (Clojure team)18:05:55

if you are consistent with how you alias across a project, you can then look at a function in any namespace and have a good sense of where it’s from

Alex Miller (Clojure team)18:05:41

dsl/syntaxy use cases are one case where you might consider bending these (core.async’s >! etc is another example)

matan18:05:10

yeah, makes sense

Alex Miller (Clojure team)18:05:19

but in that case I would explicitly :refer [select from where] those symbols rather than :refer :all

Alex Miller (Clojure team)18:05:44

so that if you encounter an unknown function in your source code and you look at the top of the file, you can understand its context

Alex Miller (Clojure team)18:05:27

so maybe the larger guideline is - ensure that functions from other namespaces have context clues (either with an alias or via explicit refer)

Alex Miller (Clojure team)18:05:06

of course, some tools like Cursive, CIDER, etc are doing sufficient analysis to help you resolve these questions in the tool, but I still think it’s a good idea

mobileink19:05:21

fwiw i tend to use both :as and :refer (with a specific list). the latter serves as notification to the reader that i intend to use those vars, but in the code i use the alias anyway.

matan17:05:01

will check there. this is a real sticking point for beginners or at least for me.

mtkp17:05:16

esp coming to a namespace that someone else has written, its easier to grok (h/update ...)

matan17:05:28

so far

(:require [honeysql.helpers :refer :all :rename {update update-sql}])
fails to make the rename.

noisesmith18:05:48

isn’t that hash-map backward?

matan18:05:09

noisesmith: not sure, it fails to make the rename either way I order this map. kind of annoying that there's no verification that would fail a rename if the symbol doesn't exist

noisesmith18:05:15

my uncertainty comes from alex’s point, about just never using the feature 😄 - I haven’t regretted avoiding all of use, refer, and rename

matan18:05:54

understood :thumbsup:

Alex Miller (Clojure team)18:05:54

personally, I would say: never rename, use aliases

matan18:05:32

well I'll stick to all the advice, many thanks @alexmiller @noisesmith @mtkp

matan18:05:38

But going forward I must understand how to systematically translate from the repl-oriented docs e.g. https://clojuredocs.org/clojure.core/alias, to the ns way of using the same idioms. The intuition still evades me...

Alex Miller (Clojure team)18:05:10

add a :, remove the quote

Alex Miller (Clojure team)18:05:37

(require '[a :as b]) => (:require [a :as b])

Alex Miller (Clojure team)18:05:06

import actually works with or without the quote in the repl, but I usually still follow the same rule so I don’t have to think about it

matan18:05:23

I wonder why is the quoting in

(require '[a :as b])
required in the first place?

Alex Miller (Clojure team)18:05:14

b/c require is a function and otherwise a and b would be evaluated

Alex Miller (Clojure team)18:05:46

import is a macro so the argument is not evaluated so doesn’t matter there

matan18:05:57

ah, right, stuff in a vector gets evaluated

matan18:05:34

well it turns out that for using the honeysql dsl, I do wish to avoid prefixing the namespace on every line and form

matan18:05:52

So I will stick only for that, to

(:require [honeysql.helpers :refer :all :rename [update sql-update]])

matan18:05:07

yet this doesn't avoid update going to honeysql

matan18:05:27

I'll take a break and have a fresh look

matan18:05:13

thanks nonetheless

Alex Miller (Clojure team)18:05:54

well, you’re referring :all, so I think that brings everything in, including update

mtkp18:05:04

np. aliases might seem annoying at first, but are incredibly helpful when revisiting code. i deal with honeysql lib about once a month, and i don't want to have to remember all the corresponding functions. imagine if you've required multiple namespaces... its better to know h/select comes from honeysql.helpers :as h and s/join comes from clojure.string :as s

matan18:05:21

I guess so, but if I'm allowed to be unprofessional for a moment, this is a really annoying part of the language and its documentation 🙂

matan18:05:43

I'm not even sure how aliasing and referring defer

Alex Miller (Clojure team)18:05:44

being annoyed is part of being a professional :)

matan18:05:21

@alexmiller oh I guess it is the opposite of that 🙂 but this is the first thing that's actually gotten me there working with clojure for 5 intensive weeks now

Alex Miller (Clojure team)18:05:53

did you try (:require [honeysql.helpers :refer :all :rename {update sql-update}]) ?

Alex Miller (Clojure team)18:05:06

:rename should be a map iirc

matan18:05:18

right, I use exactly that, wrong pasting before apologies for that

matan18:05:45

oddly (doc refer) doesn't mention the :all option

matan18:05:27

that's probably coming from the ns macro

matan18:05:02

or I totally lost it 👽

noisesmith18:05:39

:refer :all is mentioned by require’s doc

noisesmith18:05:05

peregrine.circle=> (require '[clojure.string :refer [join] :rename {join j}])
nil
peregrine.circle=> (j \, (range 10))
"0,1,2,3,4,5,6,7,8,9"

matan18:05:17

:thumbsup: what's the diff between refering and aliasing though?

noisesmith18:05:43

refer means import unqualified, alias means create an alias that maps to that ns

noisesmith18:05:09

so above j is looked up as if it belonged to that ns

noisesmith18:05:42

where if I’d remembered to include :as str string/join would be looked up differently

noisesmith18:05:50

(as I understand it…)

noisesmith18:05:27

@matan see (ns-aliases *ns*) vs. (ns-refers *ns*)

noisesmith18:05:51

each one shows you a lookup map your ns owns

noisesmith18:05:24

one is symbols to namespaces and needs a /, the other is symbols to vars

matan18:05:06

so: aliases are a mapping of symbol → namespace refers are a mapping of symbols → var

mobileink19:05:52

matan: could be wrong but i think of it as: aliase = ns -> sym, refer = var -> var (or var->sym?)

matan19:05:47

:thumbsup: :thumbsup:

matan19:05:55

just saw it now 🙂

matan18:05:26

I am going to copy paste that and put it on a wall or something 😹

matan18:05:44

curious why did classes receive special treatment? i.e. they are handled with import

noisesmith18:05:59

because classes are features of the vm

noisesmith18:05:17

namespaces are class instances that follow conventions that are specific to clojure

noisesmith18:05:32

class instance AKA “object” in normal speak

matan18:05:58

cool, makes sense :thumbsup:

noisesmith18:05:01

a nice thing about clojure (IMHO) is that even when it makes abstractions that are more useful than the raw thing the JVM provides, you can still access the JVM layer stuff directly and conveniently (and accessing clojure’s abstractions from the JVM is also reasonable)

matan18:05:07

yep, that's nice and "correct"

matan19:05:07

Thanks for all the help

matan19:05:30

I think I have a solid grasp of all those namespace manipulation things now

matan19:05:13

Now basically require just instantiates the namespace object if it's not been loaded before by other require statements in other namespaces of my project?

noisesmith19:05:43

right - and then its keywords control how it alters the namespace that called require

matan19:05:40

so no need to worry about a namespace initializing more than a single time in a project?

noisesmith19:05:09

right - but you still should never have side effects at the top level of the ns

matan19:05:55

why, actually?

matan19:05:14

because the order of loading the various namespaces is non-deterministic?

noisesmith19:05:16

because your namespace gets loaded when creating a jar, for example

noisesmith19:05:29

or if you run a linter

noisesmith19:05:52

various things can compile your ns that aren’t your running app, and clojure doesn’t have a “side effect free compile mode”

matan19:05:54

so my code needs to be explicit about any initializations then...

noisesmith19:05:06

if the code has a top level side effect, it runs when compiling the code

noisesmith19:05:58

a good patterns is to define init functions and call them in your main; as an app grows it’s useful to use a system like stuartsierra/component or integrant

matan19:05:14

Interesting, indeed I was wondering where I'd feel stuartsierra/component or integrant are necessary

matan19:05:45

why does certain code run when merely compiling though?

matan19:05:11

e.g. top level code in a namespace

matan19:05:19

:thinking_face:

mobileink19:05:08

i'm not so sure one should "never" have side effects at top level. it depends. one of the beauties of clojure is it's support for meta-programming where such side effects are to be expected. i think. maybe i'm doing it wrong. ;)

noisesmith19:05:00

what kind of side effects are you talking about here?

noisesmith19:05:57

@matan clojure intentionally doesn’t have a separate compile mode - the rules in your repl and when running a jar or when pointing clojure.jar at your file are identical because the same code is used

mobileink19:05:06

for example, adding metadata to vars, or even to namespaces.

noisesmith19:05:08

this allows meta-programming

noisesmith19:05:32

@mobileink that’s not the kind of side effect I am talking about, but they are the kinds of side effects that mean clojure shouldn’t have a separate compile mode

noisesmith19:05:59

I’m talking about “start an http server” or “send the command that launches the missiles” - side effects outside the vm

mobileink19:05:06

the other beauty is that clojure blurs the line between programming and meta programming - it's all the same, really.

noisesmith19:05:28

but launching the missiles because you ran the linter is the opposite of beautiful

mobileink19:05:06

funny is not the opposite of beautiful. simple_smile

mobileink19:05:08

ok, that's different -unintended side-effects.

noisesmith19:05:47

“unintended” - they intend to launch the missiles at app startup, they just forgot that clojure doesn’t have a “don’t run it just compile it” setting

noisesmith19:05:25

because that would break all are beautiful meta-programming

mobileink19:05:34

i'm talking about the kind of side-effects that are essential to the operation of clojure - which are available to programmers.

mobileink19:05:14

ah, well that depends on what you mean by "compile".

noisesmith19:05:37

I mean the thing clojure does that it calls compiling

matan19:05:56

Okay, so e.g. I can understand that lein compile would create bytecode files, and likewise creating a jar for distribution. But why would it execute that bytecode? (thus bumping into side-effecting code)

noisesmith19:05:23

@matan in order to create the byte code, it compiles your namespace

noisesmith19:05:32

in order to compile the namespace, it runs every form in the file

noisesmith19:05:40

there’s no other mode of operation

noisesmith19:05:07

if the form creates a function that isn’t called yet, or a constant, then all you mutate is the namespace and the classloader, you are fine

mobileink19:05:15

actually i think this is an area that could use more explanation for noobs. e.g. what is the relation between requiring, loading, evaluating, and compiling in Clojueville?

mobileink19:05:58

i know when i started i was puzzled by the claim that the repl is not an interpreter.

mobileink19:05:09

sure acts like one.

noisesmith19:05:26

it’s a compiler that remembers all the things you’ve compiled so far 😄

matan19:05:56

I'm slightly confused though :thinking_face: I thought only macros are evaluated at compilation. Now it is also any form at the top-level of a namespace. Is that because language designers were lazy to make a different "compile" mode for creating jars etc. or does it serve any purpose to execute "code" not macros, during compile?

noisesmith19:05:26

not too lazy - it’s an intentional design decision

noisesmith19:05:34

you can’t do lisp style meta programming without it

noisesmith19:05:42

afaik at least…

noisesmith19:05:45

it’s a lispy thing

matan19:05:02

why can't you?! you know.... macros

noisesmith19:05:16

macros are functions that return forms

noisesmith19:05:44

and inside the macro, you can access the full language

matan19:05:57

that pretty much nails it, remembering that macros are functions

noisesmith19:05:04

and def is a macro, which is defined as fully evaluating its last arg, etc.

mobileink19:05:18

@matan when you compile a macro you get a form from a form.

matan19:05:30

of course.

noisesmith19:05:49

@mobileink well, when you run one you do

noisesmith19:05:55

and you run it while compiling some other form, etc.

matan19:05:41

so macros are functions, and therefore in order for them to be evaluated at compile time, the floodgates are open to run any code at all during compilation. that right?

mobileink19:05:50

hence the diff between compile and eval, sorta?

matan19:05:14

mobileink: what diff?

mobileink19:05:49

macro: you compile (evaluate?) it and you get a form (unevaluated). then you evaluate it.

matan19:05:12

sounds interesting, but not sure I have what it takes to follow

noisesmith19:05:07

pretty much, yeah

matan19:05:41

@noisesmith thanks, I hadn't previously noticed that macros are functions and not some special form or thing

noisesmith19:05:09

if you want to be tricky, you can even call one in the normal way, you just need two throw away args

matan19:05:28

:thumbsup:

noisesmith19:05:39

peregrine.circle=> (#'or nil nil :a :b)
(clojure.core/let [or__4469__auto__ :a] (if or__4469__auto__ or__4469__auto__ (clojure.core/or :b)))
peregrine.circle=> (pprint *1)
(clojure.core/let
 [or__4469__auto__ :a]
 (if or__4469__auto__ or__4469__auto__ (clojure.core/or :b)))
nil

noisesmith19:05:44

poor man’s macroexpand

mobileink19:05:30

wait, macros are not functions.

noisesmith19:05:40

yes they are, see above

noisesmith19:05:58

they are functions with the :macro true metadata, and two special starting args that usually don’t matter

noisesmith19:05:13

the compiler handles your function differently if it sees that metadata

noisesmith19:05:50

this is heading out of #beginners territory though

matan19:05:57

so the compiler could have avoided running non-macro functions after all, I wonder why the design decision

noisesmith19:05:12

meta programming without macros is also valid

noisesmith19:05:21

clojure.core does it, at least

matan19:05:07

does it spin forms with non-macro functions and then eval it or what? I can look up in the source if only pointed in the right direction

mobileink19:05:07

i don't see how that can possibly be the case. when you apply a fn to args, the args get evsluated first. when you apply a macro to args, the args are not evaluated. i can see macro-as-metafn, but not macro-as-fn.

noisesmith19:05:38

like I said, the compiler handles your function differently if it has the :macro true metadata

noisesmith19:05:48

the compiler is what decides if your args are evaluated

noisesmith19:05:06

implementation, it’s just an fn with metadata, I promise

noisesmith19:05:26

you can even make one with defn plus adding metadata, just write it as if it were a macro

noisesmith19:05:35

(and add two throwaway args, of course)

mobileink19:05:45

ok but imho it's needlessly confusing to say that macros are functions, as if they were no different than any other fn.

noisesmith19:05:59

the difference is the compiler’s behavior

matan19:05:32

So previously we said there's only one compile mode (or something like that). So how do we frame the notion, or reconcile the facts, that we have lein compile that does one thing, and that otherwise compilation is done on-the-fly as if being only an interpreter?

matan19:05:55

Or I'm grossly wrong there...

noisesmith19:05:06

lein compile is doing the same thing the repl does

noisesmith19:05:16

it reads a form at a time and compiles it

mobileink19:05:44

@noisesmith right. which means a nacro is not a fn, and a fn is not a macro, no?

noisesmith19:05:00

(there’s a difference about a *earmuffed-var* that tells you whether to put bytecode on disk, that’s the only difference)

noisesmith19:05:25

@mobileink the difference is a key in the metadata, the code for clojure.core/fn is run to create macros

matan19:05:48

okay, so "compile" runs through all of the source files it is told to, executes all top-level statements and throws stuff into bytecode files.

mobileink19:05:19

you mean aot compile?

noisesmith19:05:36

well lein compile aot compiles, yes

noisesmith19:05:48

I think it even overrides :aot settings

matan19:05:58

and instructing the JVM or lein to run my project (or test it) executes my designated "main" or "test" functions, which executes them to their full depth.

mobileink19:05:15

ok, thought it used "aot" for that (boot guy here).

mobileink20:05:51

in any case fwiw when you eval stuff (e.g. in a repl) it gets byte compile but not wriiten to disk, i think.

noisesmith20:05:52

oh, lein compile just uses your project :aot setting, I was wrong

noisesmith20:05:18

@mobileink right, that’s because the current-file var (forget the specific name) isn’t set

noisesmith20:05:54

the thing I’m trying to emphasize is that it’s the same compiler code being used, just a small toggle for writing the class files to disk or not

matan20:05:26

:thumbsup: that part sounds clear to me

matan20:05:06

why do we need aot then, if compile spills it all to files to begin with?

noisesmith20:05:20

aot tells compile which stuff to spit

noisesmith20:05:29

see my self correction above

noisesmith20:05:57

and you really shouldn’t need to use the compile task directly, unless you are eg. replacing the jar task from scratch or something

matan20:05:40

> aot tells compile which stuff to spit doesn't compilation by default spit everything?

matan20:05:16

or does it just skip things like gen-class and java-interopy stuff

noisesmith20:05:29

no - compile is a lein task, it respects your :aot settings

matan20:05:47

no native clojure "compile" task?

noisesmith20:05:59

no - that’s all clojure.jar itself even does

noisesmith20:05:14

it’s not a separate task, it’s what happens if you invoke clojure.main

noisesmith20:05:39

big picture, clojure is a java library that creates bytecode and runs some of it

noisesmith20:05:45

and maybe puts it on disk

matan20:05:34

trying to use that frame of things 🙂

noisesmith20:05:59

sure, but there’s no “compile” command in clojure - it’s simply what clojure.main does

noisesmith20:05:13

it’s like, the point of the entirety of clojure, to compile

noisesmith20:05:03

oh, wait, there’s a compile - I totally spaced that, my bad

matan20:05:17

So I assume that e.g. lein compile does that "big picture" thing, yet it doesn't seek to actually call any main or test code when it's done, whereas e.g. lein run does seek some main or test entry point.

noisesmith20:05:29

yes, that’s true

mobileink20:05:43

@noisesmith whew! you had me sweating there.

matan20:05:27

And as you implied, e.g. lein compile might only be useful for creating a jar, readying bytecode for a downstream java compilation stage, etc, but otherwise quite useless, other than maybe as a half-way awkward means of verification of the project's code.

noisesmith20:05:11

what I was more trying to get at is usually you are just invoking clojure.main, and clojure.main decides about compile and all that

noisesmith20:05:52

I’m rusty on aot related things, because I’ve found it useful to pretty much never aot and use shims instead (whole other can of worms)

mobileink20:05:14

i dunno about lein compile, but i use aot for servlets in boot. just a little chunk of gen-class with an :impl-ns where the clj code does the work. point being that there are places in the java ecosystem where you need bits of aot code, but you don't have to aot everything.

noisesmith20:05:34

@mobileink that’s what a shim is

matan20:05:50

not sure about shims.. you use shims (https://github.com/projectodd/shimdandy (?!)) for?

noisesmith20:05:04

it’s a generic term, I don’t use that lib

mobileink20:05:41

i.e. any "container" that insists on finding byte code on disk calls for aot (e.g. servlet containers, some aws stuff).

noisesmith20:05:53

a shim is a small bit of code that adapts one system to another with minimal integration on either side

matan20:05:25

and in clojure?

mobileink20:05:29

different things imo.

noisesmith20:05:39

@mobileink right, I use jsvc on the server, and use a shim namespace that is aot compiled and does nothing but use require at runtime to load my real code

matan20:05:07

this makes me happy for not using servlet containers lol

noisesmith20:05:15

@matan it’s a generic term in programming, it’s not a specific function or library

matan20:05:21

yes I know that very well, but thhanks

noisesmith20:05:32

sorry, wrong @ on that

noisesmith20:05:00

it was a response to “different things imo” - what boot is doing there is a shim

noisesmith20:05:29

I am now officially totally tired of that word

matan20:05:20

actually https://github.com/projectodd/shimdandy makes me think that in case I provide a library to java users, and in a single project of theirs they also use some other clojure library that used a different version of clojure, that would break in an ugly way, unless I used shimdandy or something. ouch!

mobileink20:05:25

well, we can quibble about terminology, but the problem aot solves for servlets is not really translation, it's about disk.

noisesmith20:05:13

it’s a translation from what works best with clojure (source files and no precompiled byte code) and what works best with java (byte code directly available)

noisesmith20:05:01

I wouldn’t call it a translation, but an adaptation between the expectations/norms/best practices of two systems

matan20:05:41

I guess in abstract terms, in might be called translation too, kind of subtle terminology preferences it might seem

matan20:05:31

A means of bridging runtime paradigms 😅

mobileink20:05:38

there's not really any shim involved. the compiled in-memory code would work just fine, the problem is that the container cannot find it. the aot code it does find on disk is not an adapter, it's just on disk.

noisesmith20:05:49

if you are making a library, you avoid the problem shimdandy solves by never using aot

matan20:05:07

@noisesmith but then how can I expose classes? can I?

noisesmith20:05:35

by letting the app developer decide whether to aot compile

noisesmith20:05:45

if they aot compile, your classes are exposed on disk

noisesmith20:05:53

otherwise they don’t exist until clojure loads your code

noisesmith20:05:05

libs should not be making that decision

mobileink20:05:25

iow the only diff between aot code and non-aot code is that the former is on disk. no special magic.

matan20:05:27

if the consumer project is Java, I think that tactic however might be a bit obtuse

noisesmith20:05:44

@mobileink you make some good points, the rest of the clojure community calls this pattern of setting up an app with minimal aot a shim, just fyi

matan20:05:47

@mobileink I think AOT reserves some special compilation bugs that otherwise are not met

matan20:05:16

@noisesmith good to know about that terminology

mobileink20:05:22

yes, one must adapt to curren practice, drat!

noisesmith20:05:33

@matan the recommended way to use clojure from java is to load up clojure and use its require var

matan20:05:51

yes I hear you

matan20:05:02

kind of brittle and intrusive to the java consumer dudes, but I recall seeing that recommendation

noisesmith20:05:49

the problem is that if you aot, then that java program can’t use other things that also aot but with different clojure versions (or maybe they can and things break in super weird ways…)

matan20:05:08

yes, I spotted that few sentences before..

noisesmith20:05:32

got it, sorry, I’m a little scattered

matan20:05:37

not at all

matan20:05:08

but really I can imagine the looks on people's faces when they see they need to dynamically load clojure and fiddle with IFn's and stuff just to use some class provided from my clojure library 😁

noisesmith20:05:41

you could make a small java class that does it all with a name like MyProjectInit.load()

matan20:05:44

I should build a java wrappar lib myself then

noisesmith20:05:48

it would be like maybe 10 lines

noisesmith20:05:05

(you probably have seen it already)

matan20:05:20

yes, gone through it once or twice, will be implementing accordingly for the first time, in few days

noisesmith20:05:07

IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("my.awesome.ns"));
IFn init = Clojure.var("my.awesome.ns", "init");
return init
something like this

matan20:05:42

actually, I will make a java wrapper lib, so that Java projects can successfully compile while referencing my class. not sure for a moment how they might otherwise successfully compile :thinking_face: maybe otherwise I'd need to supply an interfaces jar to enable their compilation.

noisesmith20:05:18

by expecting an instance of IFn to be injected (or better yet, Callable, which IFn implements)

noisesmith20:05:48

in their tests, they can just make a random Callable if need be

matan20:05:20

yes but say I provide a class named Foo and I wish for java users to be able to use Foo like a regular class in their code ― in that case I need to wrap my clojure library with a thin Java facade myself ― and provide that facade to my Java users. To provide a seamless coding experience to them.

matan20:05:54

of course if I wanted to expose a specific function only, different story. but with classes...

noisesmith20:05:35

Ok, the thing I called “init” above can be a function that returns your functions - each one an IFn

noisesmith20:05:41

just document how to get it to return them

noisesmith20:05:03

because once init is returned, all your classes exist in the vm

noisesmith20:05:29

in fact, init doesn’t need to return them, if they are properly usable without clojure wrapping it (after creation) the java code can just go ahead and use them

matan20:05:31

yes, but java expects to know all classes already at its compile time!

noisesmith20:05:45

@matan which is why I mention Callable

noisesmith20:05:58

it’s defined in java core, tell them “this returns a Callable”

noisesmith20:05:30

java does enough late binding to make it work

noisesmith20:05:08

unless your wrapper adds real value of course… but if all you need is to provide access, just document how to access the Callable instances you create

matan20:05:48

I jumped directly to Scala, so my java is a little rusty. But what you're saying is that with Callable, Java code that refers to a class named Foo and calls to its methods, would compile even though the methods of Foo are not known at compile time?

noisesmith20:05:29

it doesn’t need to know anything about Foo

noisesmith20:05:02

all it needs to know is that the object called foo is an instance of Callable, which it knows you can .invoke and get an Object back

noisesmith20:05:11

or .call - something like this

matan20:05:27

okay, I get the breadth of possibilities thanks.

noisesmith20:05:31

they might need to do some casts, but it shouldn’t be too rough

noisesmith20:05:41

maybe more flexible to specify an IFn though (forgot callable doesn’t do args, ugh)

matan20:05:37

Yes I understand the options. I think a clean Java-idiomatic usage of library code would be more attainable through my own wrapper, which will entirely avoid the need for all of those special (or just not plain vanilla) Java ways of accessing library code.