This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-11-17
Channels
- # aleph (4)
- # announcements (2)
- # babashka (85)
- # beginners (136)
- # calva (72)
- # clj-commons (32)
- # clj-kondo (7)
- # cljs-dev (3)
- # clojure (117)
- # clojure-europe (38)
- # clojure-nl (3)
- # clojure-norway (1)
- # clojure-uk (4)
- # clojurescript (19)
- # conjure (38)
- # core-logic (2)
- # cursive (10)
- # datalevin (1)
- # datalog (1)
- # datomic (6)
- # events (2)
- # fulcro (16)
- # google-cloud (5)
- # graphql (10)
- # gratitude (3)
- # hugsql (3)
- # luminus (5)
- # membrane-term (12)
- # missionary (2)
- # nextjournal (5)
- # off-topic (3)
- # pedestal (2)
- # polylith (7)
- # portal (3)
- # re-frame (6)
- # reagent (26)
- # reclojure (8)
- # releases (3)
- # reveal (5)
- # shadow-cljs (14)
- # spacemacs (20)
- # sql (3)
- # tools-build (3)
- # web-security (9)
I very much enjoy the iterative development processes the REPL gives you. That being said, how much planning should take place on a software project? What is the right way to plan? Is planning useful in software? If so, how much planning? I'd like to see if there's any literature on this. Personal experiences are also welcome. I ask this because I have an idea for a slightly bigger project. Unless I change my mind, the end goals / requirements aren't going to change. Obviously if I were coding around a client's changing requirements, then maybe I'd want to take an agile approach.
I don't know if you've ever seen the Ron Jeffries vs Peter Norvig sudoku stuff online (http://ravimohan.blogspot.com/2007/04/learning-from-sudoku-solvers.html?m=1), but that to me highlights the folly of just trying to ‘explore’ your way to a software solution, either in terms of functionality or architecture.
There should be 10x more work on understanding the problem and the options before you ever start the repl
@c.westrom One of my top Rich Hickey talks https://www.youtube.com/watch?v=f84n5oFoZBc where he emphasizes the importance of design (what you’re calling planning). It’s tempting to REPL away. But I’ve found that my best solutions are when then they’re thought through (designed) well on paper than just typing away and rearranging code later.
I’m using the peridot library to run tests on my app backend. However, when I put a prn statement in the handlers on the server, they don’t show up during the test output. When running the tests, is there a way to look at prn statements within a handler of a request to the server?
It is going to depend on your dev environment, prn, by default will go to stdout, which is also by default where clojure.test reporting goes, but both of those things are changeable, and some tooling changes it, and some test libraries do things like capturing output if you aren't using vanilla clojure.test
clojure.test can be configured to report test results in different ways, including junit style xml, and I don't believe printed output will show up there
I also tried clojure.tools.logging/info. Still doesn’t up in the tests
That also will generally go to stdout, depending on the existence of any logging configuration
So my guess is however you are running tests, is not showing you the stdout of running tests, but directly hooking into test reporting
It does show up the prn within the test namespaces when running tests, but not the prns of the server namespaces :thinking_face:
Some time ago I did check it and couldn't find any instances in Clojure itself where that wouldn't be true.
However, nothing prevents someone from creating a custom class that implements Fn
but doesn't implement IFn
.
is there a way to convert a generic string to a literal? I am trying to have a literal which is dynamically generated at run time such that
'<symbol>
where symbol is a random string becomes an actual literalHey all - I have what may be a bug in Clojure 1.10.1… I am trying to write a macro that will replace an existing var in a namespace after emitting a warning. I noticed that if I include a form like (def something …)
inside a when
or if
block, even in the non-executing branch, the def
will still have an effect. Simple example:
user> (when false (def conj "HI!"))
WARNING: conj already refers to: #'clojure.core/conj in namespace: user, being replaced by: #'user/conj
nil
user> conj
#object[clojure.lang.Var$Unbound 0x663308db "Unbound: #'user/conj"]
The actual form I want to generate is something like
(do (when ((ns-map *ns*) 'R1-rect)
(prn "Unmapping existing var!")
(ns-unmap *ns* 'R1-rect))
(def R1-rect "hi!"))
Which works great if you do this in a do
block, but if you wrap this block in (when true …)
then you get an IllegalStateException
@U0P0TMEFJ that will have the same problem, since simply including the else
branch will cause the error before the alter-var-root
branch is executed
meaning just having def
inside any conditional at all is the problem, not the actual effect of the ns-unmap
call
sure ... but I think I mean that you probably don't want to call ns-unmap
, you probably want to call alter-var-root
maybe like this?
(defonce random-var nil)
(alter-var-root #'random-var (fn [_] :new-value))
@U0P0TMEFJ sure, that’s a good change. Can you write the conditional part now, where random-var
does not exist and you want the else
clause to create it and bind it to :new-value
?
that is what is causing the bug
The idea here is to write a form like one of these two:
(define-coordinates [x y] R2-rect)
(define-coordinates [x y] some-ns/R2-rect)
this macro will bind a bunch of functions:
x, y, dx, dy, d:dx, d:dy
for differential geometry, but it will also bind R2-rect
in the current namespace to (with-coordinate-prototype R2-rect [x y)
here is a simplified version:
(defmacro define-coordinates
[coordinate-prototype coordinate-system]
(let [sys-name (symbol (name coordinate-system))
mapping-sym (gensym)]
`(let [~mapping-sym (ns-map *ns*)]
(when (~mapping-sym '~sys-name)
(prn "Unmapping " '~sys-name)
(ns-unmap *ns* '~sys-name))
(def ~sys-name
(m/with-coordinate-prototype
~coordinate-system
~(quotify-coordinate-prototype coordinate-prototype))))))
@U0P0TMEFJ if someone calls (define-coordinates [x y] some-ns/R2-rect)
there is no problem
but I want to rebind R2-rect
in the case where R2-rect
already is imported with no prefix, like (define-coordinates [x y] R2-rect)
Vars defined with def
are interned by the compiler, so where
won't have an effect.
But you should be able to use intern
instead of def
. Alternatively, if that's possible, you might evaluate that where
during macro expansion.
@U0P0TMEFJ I don’t think so
@U2FRKM4TW trying intern
!
sorry, it was (gensym)
updated
I did that so I could use it in two separate spots, not under the same
`(defmacro define-coordinates [coordinate-prototype coordinate-system]
(let [sys-name (symbol (name coordinate-system))]
`(do (defonce ~sys-name nil)
(alter-var-root (var ~sys-name) (fn [old#]
(prn "Defining" (quote ~sys-name) "used to be" old#)
(quote [~coordinate-system ~coordinate-prototype]))))))
?? ... glad you've got it working ... I'm not sure I understand why you're unmapping something from the ns just to re-def it again ... but cool 😉 ... also, you can use mapping-sym#
in place of calling gensym
directly like that (if that's useful)@U0P0TMEFJ in the simplified form, yes, but the real one is this:
(defmacro define-coordinates
"Give some `coordinate-system` like `R2-rect` and a `coordinate-prototype` like
`[x y]` or `(up x y), `binds the following definitions into the namespace
where [[define-coordinates]] is invoked:
- `R2-rect` binds to a new version of the coordinate system with its
`coordinate-prototype` replaced by the supplied prototype
- `x` and `y` bind to coordinate functions, ie, functions from manifold point
to that particular coordinate
- `d:dx` and `d:dy` bind to the corresponding vector field procedures
- `dx` and `dy` bind to 1-forms for each coordinate."
[coordinate-prototype coordinate-system]
(let [sys-name (symbol (name coordinate-system))
coord-names (symbols-from-prototype coordinate-prototype)
vector-field-names (map vf/coordinate-name->vf-name coord-names)
form-field-names (map ff/coordinate-name->ff-name coord-names)
value-sym (gensym)
mapping-sym (gensym)]
`(let [~mapping-sym (ns-map *ns*)]
(when (~mapping-sym '~sys-name)
(log/warn "Replacing" '~sys-name "in namespace" *ns*))
(ns-unmap *ns* '~sys-name)
(intern *ns* '~sys-name
(m/with-coordinate-prototype
~coordinate-system
~(quotify-coordinate-prototype coordinate-prototype)))
(let [~value-sym
(into [] (flatten
[(coordinate-functions ~sys-name)
(vf/coordinate-system->vector-basis ~sys-name)
(ff/coordinate-system->oneform-basis ~sys-name)]))]
~@(map-indexed
(fn [i sym]
`(do
(when (~mapping-sym '~sym)
(log/warn "Replacing" '~sym "in namespace" *ns*))
(ns-unmap *ns* '~sym)
(intern *ns* '~sym (nth ~value-sym ~i))))
(concat coord-names vector-field-names form-field-names))))))
@U0P0TMEFJ I use mapping-sym
inside the ~@
block below where I generate snippets, so if I used mapping-sym#
in both places it’s used above i would get different symbols
@U2FRKM4TW looking at defonce
, I see that they are doing a slightly different trick:
(defmacro defonce
"defs name to have the root value of the expr iff the named var has no root value,
else expr is unevaluated"
{:added "1.0"}
[name expr]
`(let [v# (def ~name)]
(when-not (.hasRoot v#)
(def ~name ~expr))))
@U2FRKM4TW is it worth copying this style, and calling (def ~sym)
to get the binding in the first place?
I suppose the question is which style is more Clojurescript friendly
the macroexpansion generates a form like (intern *ns* 'sym ...)
, I just mean is that correct for redefining something in cljs
@U0P0TMEFJ the reason I was calling unmap
was because if you don’t, you get
R1-rect already refers to: #'sicmutils.env/R1-rect in namespace:
sicmutils.fdg.ch1-test
when you try to intern
. Is there some better way?
a var is a container for a value that has update semantics ... if you want to change the root value of a var you can use alter-var-root
to do that ... and defonce
to ensure that it already exists ... I would have thought that unmapping things from the namespace is a dev time reply thing to do to avoid restarting the jvm ... I wouldn't expect to use that in production code ... it just seems like an odd thing to do to me ... but if you have something you're happy with, then you should totally ignore me 😉
@U0P0TMEFJ the library here is an interactive system for doing math and physics, sort of like Mathematica
@U0P0TMEFJ so this form is either a supplement to your namespace definition and should live near the top of a file, or something you would type during REPL investigation, or in a notebook form
Hmm, interesting how defonce
is implemented. It suggests that my explanation might be incorrect.
Ah, wait - that's a completely orthogonal thing.
Your initial warning was not because def
is evaluated during compilation but rather because you replace conj
that came from clojure.core
.
And with that in mind, what defonce
does won't help you if you keep using names like conj
- that very warning will still be there. And there will be no warnings if you use your old approach but with names that don't shadow imported things.
yeah, I am trying to bind something, but only if it does not exist
intern
it is then. :) Unless you confirm that defonce
somehow works there, which it shouldn't.
so def
was in the else clause, where I had determined that there was no binding; but the compiler sees def
and creates an unbound var, so errors even if the form is in the else
clause of a conditional
@U2FRKM4TW yup! of course Clojurescript does not like my conditional test 🙂
62 | (let [e (e/coordinate-system->vector-basis coordsys)]
63 | ((L2 mass metric) ((point coordsys) x) (* e v)))))
64 |
65 | (define-coordinates t e/R1-rect)
-------^------------------------------------------------------------------------
Encountered error when macroexpanding cljs.core/ns-unmap.
AssertionError: Assert failed: Arguments to ns-unmap must be quoted symbols
Yeah, you can't do that in CLJS.
If you need that to be cross-platform, you will have to evaluate that when
during macro expansion. So that the resulting code either has the (def ...)
itself, without when
, or has nothing at all.
@U2FRKM4TW if I am lucky, maybe cljs will not error on a redefinition
and I can just emit def
@U2FRKM4TW nice, all done now. in Clojurescript it is NOT an error, just a warning as you say, to redefine, so I don’t need to do these shenanigans
final form
well, probably needs slightly more tidying, but this works.
this new version will only take the intern
branch if you are redefining something that was imported for somewhere else. If you are overwriting something defined in the namespace itself (or running the form twice), no warnings, just like normal def
behavior
Wait, it's is also a warning in CLJ:
Clojure 1.10.3
user=> (def conj 1)
WARNING: conj already refers to: #'clojure.core/conj in namespace: user, being replaced by: #'user/conj
#'user/conj
user=> conj
1
It's not an error.@U2FRKM4TW that is only for clojure.core
if you redefine something from a non clojure.core namespace it errors
it was a good tip though to move the *ns*
inspection to macroexpand time, of course everything I need has to be there once the namespace is already defined
where’s the best place to talk about/ask questions about the @ptaoussanis library Sente? moved to #off-topic
The README says to just use GitHub issues for questions: > Please use the project's https://github.com/ptaoussanis/sente/issues for all questions, ideas, etc.
i saw that! i was hoping to chat with folks who use it, who might have different opinions/use cases than just Peter (not to belittle his work or his usage) as subtleties in a library and workflow can come out of not being the implementor
I’ll open an issue about my situation tho, see what’s good
didn’t know that channel existed, so perfect
I'd like to play with a Java package through Clojure, and I've been spending a lot of time struggling to even load the Java package's classes into my repl. (I have very little direct experience with Java and this is my first time using deps.edn
.) this is the package: https://github.com/jkotlinski/lsdpatch/, and this is my deps.edn
:
{:deps
{lsdpatch/lsdpatch {:git/url ""
:git/sha "12adb3b4f0fd48dc3c5a03c1fa78f15a2185f196"
:git/tag "v1.11.6"}}}
there's a class LSDSavFile
, defined here: https://github.com/jkotlinski/lsdpatch/blob/v1.11.6/src/main/java/Document/LSDSavFile.java what is the correct way to import that LSDSavFile
class?you can't (easily) load a Java project via a git dep
Java source code must be compiled to .class files before it can be loaded
most Java libraries have already done that, bundled them into a jar, and made them available in a public Maven repository somewhere
this one does not apparently, but based on the readme, you could git clone the repo, run mvn package
to create the jar
you could then add it to your project with {:deps {lsdpatch/lsdpatch {:local/root "path/to/that.jar"}}}
or you could mvn install
to package and install locally, then
{:deps {com.littlesounddj/lsdpatch {:mvn/version "1.11.6"}}}
@pclalv_clojurians Try using jitpack
{:mvn/repos {"jitpack" {:url ""}}
:deps {com.github.jkotlinski/lsdpatch {:mvn/version "v1.12.0"}}
➜ ~ clj
Clojure 1.10.3
user=> (import Document.LSDSavFile)
Document.LSDSavFile
user=> (LSDSavFile.)
#object[Document.LSDSavFile 0x385ef531 "Document.LSDSavFile@385ef531"]
user=>
is clojure.core/read
able to read conditionals from any feature?
clojure.core/read
([] [stream] [stream eof-error? eof-value] [stream eof-error? eof-value recursive?] [opts stream])
Reads the next object from stream, which must be an instance of
java.io.PushbackReader or some derivee. stream defaults to the
current value of *in*.
Opts is a persistent map with valid keys:
:read-cond - :allow to process reader conditionals, or
:preserve to keep all branches
:features - persistent set of feature keywords for reader conditionals
:eof - on eof, return value unless :eofthrow, then throw.
if not specified, will throw
Note that read can execute code (controlled by *read-eval*),
and as such should be used only with trusted sources.
For data structure interop use clojure.edn/read
user=> (slurp "/tmp/test.cljc")
"#?(:clj (ns foo)\n :cljs (ns bar)\n )\n\n"
user=> (slurp "/tmp/test.cljc")
"#?(:clj (ns foo)\n :cljs (ns bar)\n )\n\n"
user=> (require '[ :as io])
nil
user=> (read {:read-cond :allow :features #{:cljs}} (clojure.lang.LineNumberingPushbackReader. (io/reader "/tmp/test.cljc")))
(ns foo)
This seemed to suggest so:
:features - persistent set of feature keywords for reader conditionals
It does work when I put the :cljs
one first in the code, but it seems the :clj
feature doesn't get replaced by the provided option map
yes, you can add features but not remove :clj
tools.reader is more generic