This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-05-05
Channels
- # aws-lambda (1)
- # beginners (294)
- # boot (35)
- # cider (19)
- # cljs-dev (39)
- # cljsrn (7)
- # clojars (48)
- # clojure (266)
- # clojure-android (1)
- # clojure-brasil (1)
- # clojure-france (2)
- # clojure-greece (5)
- # clojure-italy (7)
- # clojure-mexico (1)
- # clojure-russia (24)
- # clojure-spec (10)
- # clojure-uk (31)
- # clojurescript (134)
- # consulting (7)
- # cursive (69)
- # datomic (20)
- # emacs (57)
- # events (2)
- # figwheel (2)
- # hoplon (1)
- # jobs-discuss (19)
- # luminus (33)
- # lumo (18)
- # mount (1)
- # off-topic (32)
- # om (5)
- # onyx (27)
- # pedestal (15)
- # re-frame (12)
- # reagent (28)
- # rum (2)
- # schema (2)
- # spacemacs (9)
- # unrepl (2)
- # untangled (7)
- # vim (5)
- # yada (4)
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.
rishat: to me, having a deploy break because a library was changed since the last deploy seems unreasonable
see https://github.com/xsc/lein-ancient and https://github.com/martinklepsch/boot-deps
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}])
curlyfry: I consider myself lazy, yet even I wouldn't use this hack 🙂 so many other routine things I'd address first 🙂 anyway cute 🙂
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.
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.
And specifically I would like to do something like:
(:require [honeysql.helpers :refer :rename [update sql-update]])
docs tend to (require ... :refer :all)
or (use ...)
for convenience but in practice i pretty much always alias, especially 3rd party libs.
https://stuartsierra.com/2016/clojure-how-to-ns.html is excellent advice
alexmiller: well, writings like > Do not :refer :all don't really help with understanding...
:refer :all means that you have added many names to your namespace with no indication of where they came from
I strongly prefer to alias a required namespace and use foo/the-thing instead
Unfortunately some libraries kind of suggest the wrong way, case in point https://github.com/jkk/honeysql#usage
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
dsl/syntaxy use cases are one case where you might consider bending these (core.async’s >! etc is another example)
but in that case I would explicitly :refer [select from where]
those symbols rather than :refer :all
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
so maybe the larger guideline is - ensure that functions from other namespaces have context clues (either with an alias or via explicit refer)
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
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.
esp coming to a namespace that someone else has written, its easier to grok (h/update ...)
so far
(:require [honeysql.helpers :refer :all :rename {update update-sql}])
fails to make the rename.isn’t that hash-map backward?
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
my uncertainty comes from alex’s point, about just never using the feature 😄 - I haven’t regretted avoiding all of use, refer, and rename
personally, I would say: never rename, use aliases
well I'll stick to all the advice, many thanks @alexmiller @noisesmith @mtkp
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...
add a :, remove the quote
(require '[a :as b])
=> (:require [a :as b])
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
b/c require
is a function and otherwise a
and b
would be evaluated
import
is a macro so the argument is not evaluated so doesn’t matter there
well it turns out that for using the honeysql dsl, I do wish to avoid prefixing the namespace on every line and form
So I will stick only for that, to
(:require [honeysql.helpers :refer :all :rename [update sql-update]])
well, you’re referring :all, so I think that brings everything in, including update
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
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 🙂
being annoyed is part of being a professional :)
@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
did you try (:require [honeysql.helpers :refer :all :rename {update sql-update}])
?
:rename should be a map iirc
:refer :all is mentioned by require’s doc
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"
refer means import unqualified, alias means create an alias that maps to that ns
so above j is looked up as if it belonged to that ns
where if I’d remembered to include :as str
string/join would be looked up differently
(as I understand it…)
@matan see (ns-aliases *ns*)
vs. (ns-refers *ns*)
each one shows you a lookup map your ns owns
one is symbols to namespaces and needs a /, the other is symbols to vars
✨ so:
aliases are a mapping of symbol → namespace
refers are a mapping of symbols → var
matan: could be wrong but i think of it as: aliase = ns -> sym, refer = var -> var (or var->sym?)
right
because classes are features of the vm
namespaces are class instances that follow conventions that are specific to clojure
class instance AKA “object” in normal speak
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)
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?
right - and then its keywords control how it alters the namespace that called require
so no need to worry about a namespace initializing more than a single time in a project?
right - but you still should never have side effects at the top level of the ns
because your namespace gets loaded when creating a jar, for example
or if you run a linter
various things can compile your ns that aren’t your running app, and clojure doesn’t have a “side effect free compile mode”
if the code has a top level side effect, it runs when compiling the code
indeed!
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
Interesting, indeed I was wondering where I'd feel stuartsierra/component or integrant are necessary
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. ;)
what kind of side effects are you talking about here?
@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
this allows meta-programming
@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
I’m talking about “start an http server” or “send the command that launches the missiles” - side effects outside the vm
the other beauty is that clojure blurs the line between programming and meta programming - it's all the same, really.
but launching the missiles because you ran the linter is the opposite of beautiful
“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
because that would break all are beautiful meta-programming
i'm talking about the kind of side-effects that are essential to the operation of clojure - which are available to programmers.
I mean the thing clojure does that it calls compiling
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)
@matan in order to create the byte code, it compiles your namespace
in order to compile the namespace, it runs every form in the file
there’s no other mode of operation
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
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?
i know when i started i was puzzled by the claim that the repl is not an interpreter.
it’s a compiler that remembers all the things you’ve compiled so far 😄
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?
not too lazy - it’s an intentional design decision
you can’t do lisp style meta programming without it
afaik at least…
it’s a lispy thing
macros are functions that return forms
and inside the macro, you can access the full language
and def is a macro, which is defined as fully evaluating its last arg, etc.
@mobileink well, when you run one you do
and you run it while compiling some other form, etc.
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?
macro: you compile (evaluate?) it and you get a form (unevaluated). then you evaluate it.
pretty much, yeah
@noisesmith thanks, I hadn't previously noticed that macros are functions and not some special form or thing
if you want to be tricky, you can even call one in the normal way, you just need two throw away args
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
poor man’s macroexpand
yes they are, see above
they are functions with the :macro true
metadata, and two special starting args that usually don’t matter
the compiler handles your function differently if it sees that metadata
this is heading out of #beginners territory though
so the compiler could have avoided running non-macro functions after all, I wonder why the design decision
meta programming without macros is also valid
clojure.core does it, at least
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
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.
like I said, the compiler handles your function differently if it has the :macro true metadata
the compiler is what decides if your args are evaluated
implementation, it’s just an fn with metadata, I promise
you can even make one with defn plus adding metadata, just write it as if it were a macro
(and add two throwaway args, of course)
ok but imho it's needlessly confusing to say that macros are functions, as if they were no different than any other fn.
the difference is the compiler’s behavior
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?
lein compile is doing the same thing the repl does
it reads a form at a time and compiles it
@noisesmith right. which means a nacro is not a fn, and a fn is not a macro, no?
(there’s a difference about a *earmuffed-var*
that tells you whether to put bytecode on disk, that’s the only difference)
@mobileink the difference is a key in the metadata, the code for clojure.core/fn is run to create macros
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.
well lein compile aot compiles, yes
I think it even overrides :aot settings
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.
in any case fwiw when you eval stuff (e.g. in a repl) it gets byte compile but not wriiten to disk, i think.
oh, lein compile
just uses your project :aot setting, I was wrong
@mobileink right, that’s because the current-file var (forget the specific name) isn’t set
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
aot tells compile which stuff to spit
see my self correction above
and you really shouldn’t need to use the compile task directly, unless you are eg. replacing the jar task from scratch or something
> aot tells compile which stuff to spit doesn't compilation by default spit everything?
no - compile is a lein task, it respects your :aot settings
no - that’s all clojure.jar itself even does
it’s not a separate task, it’s what happens if you invoke clojure.main
big picture, clojure is a java library that creates bytecode and runs some of it
and maybe puts it on disk
sure, but there’s no “compile” command in clojure - it’s simply what clojure.main does
it’s like, the point of the entirety of clojure, to compile
oh, wait, there’s a compile
- I totally spaced that, my bad
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.
yes, that’s true
@noisesmith whew! you had me sweating there.
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.
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
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)
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.
@mobileink that’s what a shim is
not sure about shims.. you use shims (https://github.com/projectodd/shimdandy (?!)) for?
it’s a generic term, I don’t use that lib
i.e. any "container" that insists on finding byte code on disk calls for aot (e.g. servlet containers, some aws stuff).
a shim is a small bit of code that adapts one system to another with minimal integration on either side
@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
@matan it’s a generic term in programming, it’s not a specific function or library
sorry, wrong @ on that
it was a response to “different things imo” - what boot is doing there is a shim
I am now officially totally tired of that word
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!
well, we can quibble about terminology, but the problem aot solves for servlets is not really translation, it's about disk.
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)
I wouldn’t call it a translation, but an adaptation between the expectations/norms/best practices of two systems
I guess in abstract terms, in might be called translation too, kind of subtle terminology preferences it might seem
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.
if you are making a library, you avoid the problem shimdandy solves by never using aot
@noisesmith but then how can I expose classes? can I?
by letting the app developer decide whether to aot compile
if they aot compile, your classes are exposed on disk
otherwise they don’t exist until clojure loads your code
libs should not be making that decision
iow the only diff between aot code and non-aot code is that the former is on disk. no special magic.
@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
@mobileink I think AOT reserves some special compilation bugs that otherwise are not met
@noisesmith good to know about that terminology
@matan the recommended way to use clojure from java is to load up clojure and use its require var
kind of brittle and intrusive to the java consumer dudes, but I recall seeing that recommendation
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…)
got it, sorry, I’m a little scattered
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 😁
you could make a small java class that does it all with a name like MyProjectInit.load()
it would be like maybe 10 lines
@matan following this guide should be easy https://clojure.org/reference/java_interop#_calling_clojure_from_java
(you probably have seen it already)
yes, gone through it once or twice, will be implementing accordingly for the first time, in few days
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 thisactually, 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.
by expecting an instance of IFn to be injected (or better yet, Callable, which IFn implements)
in their tests, they can just make a random Callable if need be
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.
of course if I wanted to expose a specific function only, different story. but with classes...
Ok, the thing I called “init” above can be a function that returns your functions - each one an IFn
just document how to get it to return them
because once init is returned, all your classes exist in the vm
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
@matan which is why I mention Callable
it’s defined in java core, tell them “this returns a Callable”
java does enough late binding to make it work
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
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?
it doesn’t need to know anything about Foo
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
or .call - something like this
they might need to do some casts, but it shouldn’t be too rough
maybe more flexible to specify an IFn though (forgot callable doesn’t do args, ugh)