Fork me on GitHub
#clojure
<
2021-11-17
>
West03:11:11

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.

thom08:11:35

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.

Alex Miller (Clojure team)03:11:54

There should be 10x more work on understanding the problem and the options before you ever start the repl

☝️ 3
💯 1
truestory 1
2
bananadance 1
🧠 1
indy04:11:59

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

👀 1
zendevil.eth07:11:10

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?

hiredman07:11:53

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

hiredman07:11:51

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

zendevil.eth07:11:46

I also tried clojure.tools.logging/info. Still doesn’t up in the tests

hiredman07:11:59

That also will generally go to stdout, depending on the existence of any logging configuration

hiredman07:11:51

So my guess is however you are running tests, is not showing you the stdout of running tests, but directly hooking into test reporting

zendevil.eth11:11:00

It does show up the prn within the test namespaces when running tests, but not the prns of the server namespaces :thinking_face:

genmeblog09:11:14

I'm having some doubts. Does `ifn?` return true for every case `fn?` returns true?

mpenet09:11:40

well, you asked the other way around actually

genmeblog09:11:41

the other way

mpenet09:11:46

yeah it should then

genmeblog09:11:14

The underlying interfaces are not related, that's why I'm asking.

p-himik09:11:22

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.

👍 2
genmeblog09:11:17

Thanks. I believe I shouldn't care of such cases.

ChillPillzKillzBillz10:11:15

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 literal

p-himik10:11:25

Just call symbol? (symbol that-random-string).

Sam Ritchie12:11:04

Hey 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"]

Sam Ritchie12:11:48

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

Ed12:11:48

do you want alter-var-root?

Sam Ritchie12:11:37

@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

Sam Ritchie12:11:16

meaning just having def inside any conditional at all is the problem, not the actual effect of the ns-unmap call

Ed12:11:22

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

Sam Ritchie12:11:58

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

Sam Ritchie12:11:06

that is what is causing the bug

Ed12:11:58

why would the var not exist? ... how is it getting created?

Ed12:11:15

I think I need some more context to help

Sam Ritchie12:11:40

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)

Sam Ritchie12:11:14

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)

Ed12:11:53

what does define-coordinates look like so far?

Sam Ritchie12:11:50

here is a simplified version:

Sam Ritchie12:11:00

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

Sam Ritchie12:11:33

@U0P0TMEFJ if someone calls (define-coordinates [x y] some-ns/R2-rect) there is no problem

Sam Ritchie12:11:53

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)

Ed12:11:56

that form looks weird ... like it's trying to define something in another namespace

p-himik12:11:12

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.

Ed12:11:30

I think the simplied code you posted is a bit too simplified ... mapping-sym isn`t set

Sam Ritchie12:11:12

sorry, it was (gensym)

Sam Ritchie12:11:41

I did that so I could use it in two separate spots, not under the same

`

Sam Ritchie12:11:10

@U2FRKM4TW intern was the ticket

👍 1
Ed12:11:54

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

Sam Ritchie12:11:30

@U0P0TMEFJ in the simplified form, yes, but the real one is this:

Sam Ritchie12:11:40

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

Sam Ritchie12:11:24

@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

Sam Ritchie12:11:44

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

Sam Ritchie12:11:11

@U2FRKM4TW is it worth copying this style, and calling (def ~sym) to get the binding in the first place?

Sam Ritchie12:11:29

I suppose the question is which style is more Clojurescript friendly

Ed12:11:43

clojurescript doesn't have vars

Ed12:11:55

or macros

Ed12:11:20

well ... you run macros on the jvm ... right?

Sam Ritchie12:11:46

the macroexpansion generates a form like (intern *ns* 'sym ...) , I just mean is that correct for redefining something in cljs

Sam Ritchie12:11:11

@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

Sam Ritchie12:11:57

when you try to intern. Is there some better way?

Ed12:11:41

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 😉

Sam Ritchie12:11:00

@U0P0TMEFJ the library here is an interactive system for doing math and physics, sort of like Mathematica

Sam Ritchie12:11:35

@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

p-himik13:11:44

Hmm, interesting how defonce is implemented. It suggests that my explanation might be incorrect.

p-himik13:11:51

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.

p-himik13:11:28

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.

Sam Ritchie13:11:30

yeah, I am trying to bind something, but only if it does not exist

p-himik13:11:15

intern it is then. :) Unless you confirm that defonce somehow works there, which it shouldn't.

Sam Ritchie13:11:15

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

Sam Ritchie13:11:44

@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

p-himik13:11:36

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.

Sam Ritchie13:11:39

@U2FRKM4TW if I am lucky, maybe cljs will not error on a redefinition

Sam Ritchie13:11:42

and I can just emit def

p-himik13:11:06

It will, I just checked. (defonce conj 1) - same warning.

Sam Ritchie14:11:29

@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

Sam Ritchie14:11:02

well, probably needs slightly more tidying, but this works.

Sam Ritchie14:11:40

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

p-himik14:11:30

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.

Sam Ritchie15:11:59

@U2FRKM4TW that is only for clojure.core

Sam Ritchie15:11:09

if you redefine something from a non clojure.core namespace it errors

Sam Ritchie15:11:15

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

p-himik15:11:50

I see, didn't realize it becomes a syntax error.

Noah Bogart14:11:24

where’s the best place to talk about/ask questions about the @ptaoussanis library Sente? moved to #off-topic

p-himik14:11:52

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.

Noah Bogart14:11:06

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

Noah Bogart14:11:55

I’ll open an issue about my situation tho, see what’s good

p-himik14:11:04

Ah, in this case I'd probably ask in #off-topic

Noah Bogart14:11:22

didn’t know that channel existed, so perfect

paul a15:11:20

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?

Alex Miller (Clojure team)15:11:40

you can't (easily) load a Java project via a git dep

Alex Miller (Clojure team)15:11:56

Java source code must be compiled to .class files before it can be loaded

💡 1
Alex Miller (Clojure team)15:11:23

most Java libraries have already done that, bundled them into a jar, and made them available in a public Maven repository somewhere

Alex Miller (Clojure team)15:11:33

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"}}}

Alex Miller (Clojure team)15:11:20

or you could mvn install to package and install locally, then {:deps {com.littlesounddj/lsdpatch {:mvn/version "1.11.6"}}}

emccue15:11:15

its a repository which will build java projects from git on demand

emccue15:11:40

if that doesn’t work, then yes what alex said

paul a15:11:19

thank you both 🙇 that's a lot of good information for me to work with!

emccue15:11:09

{:mvn/repos {"jitpack" {:url ""}}
 :deps      {com.github.jkotlinski/lsdpatch {:mvn/version "v1.12.0"}}

😎 1
emccue15:11:12

➜  ~ clj
Clojure 1.10.3
user=> (import Document.LSDSavFile)
Document.LSDSavFile
user=> (LSDSavFile.)
#object[Document.LSDSavFile 0x385ef531 "Document.LSDSavFile@385ef531"]
user=>

paul a21:11:03

delayed, but I'm just now realizing how cool jitpack is. amazing!

borkdude22:11:15

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)

borkdude22:11:47

This seemed to suggest so:

:features - persistent set of feature keywords for reader conditionals

lilactown22:11:27

hmm that should read (ns bar) instead of (ns foo)

borkdude22:11:22

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

borkdude22:11:28

That seems a deliberate choice in LispReader.java

Alex Miller (Clojure team)22:11:56

yes, you can add features but not remove :clj

Alex Miller (Clojure team)22:11:07

tools.reader is more generic

borkdude22:11:53

I guess you can use :read-cond :preserve and do some processing