Fork me on GitHub
#beginners
<
2023-10-06
>
Ingy döt Net14:10:32

I wish to

(defn Long [x] ...)
But I get
Expecting var, but Long is mapped to class java.lang.Long
even with
(ns foo
  (:refer-clojure :exclude [Long]))
Is there a way to do this?

Dane Filipczak14:10:22

you could unmap Long

(ns-unmap *ns* 'Long)
(defn Long [x])
but that's unlikely to be a good idea

Alex Miller (Clojure team)14:10:18

it's likely to be a bad idea even :)

Ingy döt Net15:10:58

I just haven't run into any symbol limitations like this yet.

Ingy döt Net15:10:32

I'm defining a yamlscript.ast namespace with names like

Map Vec Str Key Sym Char Bool Long
and Long was the first one to bite me. I can use LNum or somesuch. was just a but surprised, but I can see how it might lead to chaos. Wondering what other (kinds of) symbols are off limits in my own namespace...

andy.fingerhut15:10:38

Clojure by default imports many classes in the java.lang package, including Long Integer Byte

andy.fingerhut15:10:45

probably a few dozen others.

Ingy döt Net15:10:25

I know. That's why I added "(kinds of)"

kennytilton15:10:35

Reminds me of when we had just started with Common Lisp and one of us unbound t. New languages are fun!

andy.fingerhut15:10:41

I am trying to find where in the Clojure implementation it does that, which should lead to a straightforward method of knowing the complete list of such names.

Ingy döt Net15:10:00

iow, are there other kinds of symbols besides classes that will cause the same kind of issue

Ingy döt Net15:10:06

(->> *ns*
  ns-map
  seq
  (filter #(= (type (second %)) Class))
  (map first)
  sort
  count
  )
=> 96

Ingy döt Net15:10:32

(AbstractMethodError Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError BigDecimal BigInteger Boolean Byte Callable CharSequence Character Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException CloneNotSupportedException Cloneable Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package Process ProcessBuilder Readable Runnable Runtime RuntimeException RuntimePermission SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread Thread$State Thread$UncaughtExceptionHandler ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void)

3
andy.fingerhut15:10:01

There are also symbols that you can't even really defn, but no error when you try, e.g.:

user=> (defn try [x] (inc x))
#'user/try
user=> (try 5)
5

Ingy döt Net15:10:20

the special forms?

andy.fingerhut15:10:35

special-symbol? return true for that kind of symbol, e.g:

user=> (special-symbol? 'try)
true

Ingy döt Net15:10:55

(Enum Class Error Runtime Cloneable Object Process Double Callable String Void Override Iterable Runnable System Throwable Deprecated Thread Package Readable Boolean Math Comparable Integer Short Number Character Exception Float Long Compiler Appendable Byte)
Are the single word ones.

phill15:10:28

so the custom of lowercase-hyphenated names in Clojure side-steps the possibility of such abstruse collisions

Ingy döt Net15:10:33

Nod. I have my reasons for using these symbols in this specific case.

Ingy döt Net15:10:11

@U0CMVHBL2 right, special form symbols are not looked up from a namespace.

souenzzo10:10:15

note: most of limitations discussed here are more like "implementation details" I believe that (defn Long [x] ...) works in #C03S1L9DN and probably #C03A6GE8D32 btw @U05H8N9V0HZ > I'm defining a yamlscript.ast namespace with names like Seems that you are working in some kind of DSL Did you tried #C015LCR9MHD (the engine behind #CLX41ASCS)? I think that this tool will allow you to do that kind of thing.

Ingy döt Net15:10:47

@U2J4FRT2T ys and libyamlscript are compiler/evaluators that use SCI for evaluation. But I wasn't aware that you could (defn Long ...) with SCI. Thanks!

$ bb -e "    
(defn Long [x] (+ x 1))
(Long 41)
"
42
.
$ ys -Ee "
defn Long[x]: (x + 1)
Long: 41
"
42

Ingy döt Net15:10:12

But the YAMLScript compiler itself uses JVM Clojure so it can use GraalVM to compile to the native ys and libyamlscript.

Mathias18:10:43

Hello clojurians, I'm relatively new to the world of programming and web development. My goal is to be a proficient fullstack web developer Is there a roadmap / milestone layout / checklist that I can follow on the path to proficiency with Clojure in the domain of web development?

seancorfield20:10:33

This gets asked from time-to-time but there's really no consensus on answers... Some people might suggest the Web Development with Clojure book but I think the underlying collection of libraries is "too much" for a beginner. I think Getting Clojure or Living Clojure are good books to start with. Full-stack in the Clojure world means ClojureScript on the frontend and that's a whole different beast in terms of tooling but you could try reading the shadow-cljs docs and see how you feel about it. On the backend, Ring is the underpinning for everything else but you'll have a choice of routing libraries and lifecycle management libraries etc. I often point beginners to https://github.com/seancorfield/usermanager-example which uses Ring + Compojure + Component (+ next.jdbc for the database and Selmer for the HTML templating). There's a link in the README to a variant of that app that uses Integrant and Reitit (instead of Component and Compojure).

Mathias21:10:19

Could you give an example path of what projects I could work on to apply what I learn maybe?

seancorfield21:10:52

I'm not sure what you mean... specific existing projects you could contribute to, or ideas of new projects you could build? (I don't really have suggestions of either -- most of the former out there are libraries, not apps)

Ben Lieberman21:10:44

https://developer.mozilla.org/en-US/docs/Learn could be a good general reference for web development, and for Clojurescript I would second Sean's rec to read the https://shadow-cljs.github.io/docs/UsersGuide.html. On the corresponding GitHub repo there's also a bunch of sample projects.

Mathias21:10:14

Would you say there are milestones I can keep in mind at least? I've heard projects are an effective way to assert understanding and set milestones

Ben Lieberman21:10:14

Personal opinion as an advanced beginner but I think that's highly dependent on your learning style. I wouldn't get too bogged down in a project-oriented mode of learning, unless they are projects you are personally interested in. I've done a lot of tutorials and the like, and I have learned stuff, but the final product always feels a bit unsatisfying to me. YMMV, though. Clojure's a highly dynamic language by design and I think you can learn a lot by finding a good REPL workflow that works for you. Plenty of content out there on that subject.

practicalli-johnny05:10:01

I would recommend starting on the backend of the full stack journey, as it is arguably simpler (and a smaller set of choices) than the front end. There are many technology choices in the frontend (html, htmlx, JavaScript, react, react native, vue, etc) and more options in how humans and devices will interact. Creating an API based backend makes it relatively simple to build whatever kind of front end is desirable. Common backend aspects include • Understand the fundamentals of ring • Choose a HTTP server (http-kit, jetty) and adapt and learn how to manage it via the REPL (reload the server app without having to restart the REPL (dynamic var, atom, component library (donut, integrant, mount, etc.) • Routing requests (reitit & reitit-ring for a data centric config, or composure for macro abstraction) • Writing request handlers (functions) • Writing middleware (functions) • Testing handlers (test functions) • Open API (swagger) Practicalli Project templates has a service template that generates a new project with a working API example with the above aspects (with a choice of atom, donut system or integrant to manage components of the system) https://practical.li/clojure/clojure-cli/projects/templates/practicalli/ Additional backend aspects to consider • Persistence - most common is next.jdbc with a relational database, e.g postgresql or maria DB. Also consider web-based persistence services like firebase (useful for richer web UI & mobile apps ) • Authentication and authorization - buddy, friend and similar libraries. Also Java Web Service tokens and OAuth are quite prevalent • Data streaming - ingesting and producing streams of data especially useful for large data processing Once there is the basics of an API then decide what kind of front end is useful and what is the target platform (web browser, mobile, everything) • Server-side content - simplest approach, but static (although htmlx or JavaScript enables a richer interaction) • ClojureScript frontend • ClojureScript with react.js - start with reagent and then consider larger libraries such as re-frame, rum, etc if more stateful and involved UI workflows are required • JavaScript (or any other frontend technology that can consume APIs Other useful examples • https://kit-clj.github.io/ to create projects from templates with a range of different options, providing example code showing how libraries can be wired together • Various examples of the User Manage project are published on GitHub, with different component libraries and other features Hopefully this has given some further insight into an approach, although anyone Shou consider what goals are valuable to achieve and form the community opinions into their own learning path.

💯 2
gratitude 1
2
Mathias10:10:32

Thank you all 😁

xbrln09:10:43

This might also help, especially to understand how an HTTP request works, which is fundamental for a web API or App. All the best !! 🙂 https://ericnormand.me/guide/clojure-web-tutorial

Matthew Twomey20:10:11

I am trying to “re-pull” down a library, referenced in deps from Github. Initially, the library pulled down fine. Now I want to repeat that process from scratch. It had pulled down into ~/.m2/repository initially. I deleted its specific directory in there. I also wiped out ~/.clojure/.cpcache. However - somehow it’s still cached somewhere. My test still works, without pulling it down again. So where else should I be looking here?

hiredman20:10:51

git checkouts go in ~/.gitlibs

Matthew Twomey20:10:21

ha ha - ok thank you I thought I was going crazy 🙂

hiredman20:10:23

https://github.com/clojure/tools.gitlibs is the library that is used to manage git deps

Matthew Twomey20:10:55

Oh nice, bonus! Thank you.

Matthew Twomey20:10:41

Style question: I have a function main-fn that uses a filter-fn in a map. That filter is based on arguments passed into main-fn. At first, I just defined filter-fn in my let binding and then proceeded to use it successfully in my map. Then, instead of defining it in my let binding, I just defined it with defn at the top of the body of my main-fn. This also works fine. I get a clj-kondo warning about this though, so I’m wondering if this is “bad form” or if there is a downside to this?

borkdude20:10:13

what warning do you get exactly?

borkdude20:10:18

and for what code exactly

Matthew Twomey20:10:31

Just an inline def warning

borkdude20:10:07

oh yes, that. just use a let binding, vars should be defined top level, not within function bodies

hiredman20:10:09

def and things derived from it like defn always create global bindings of names to values

Matthew Twomey20:10:18

Ok, here’s the actual code (maybe I shouldn’t have tried to de-mystify it):

(defn replace-in-file [file string-regex replacement-string]
  (defn replace-in-string [string]
    (let [replaced-string (str/replace string string-regex replacement-string)]
      (when (not= replaced-string string)
        (println-now (<< "Replaced string in {{file|qq}}")))
      replaced-string))

  (let [contents (str/split (slurp file) #"\n")
        updated-contents (map replace-in-string contents)]
    (spit file (str/join "\n" updated-contents))))

hiredman20:10:45

yeah, never but a defn in a def / defn

Matthew Twomey20:10:49

So are we saying that the defn replace-in-string function will be globally available after evaluating?

hiredman20:10:01

not exactly

hiredman20:10:23

the name will be created and globally visible, but the value won't be assigned to it until after repace-in-file has run

hiredman20:10:50

but it will hang on to the value more or less forever

Matthew Twomey20:10:18

Ok, so all things considered - it’s better to just keep it to a let in this case it sounds like.

hiredman20:10:00

(defn f [] (defn g [])) is basically (do (declare g) (defn f [] (alter-var-root #'g (constantly (fn [])))))

1
henrik20:10:20

You can use letfn, which gives you some of the affordances of an inline defn.

👀 1
Matthew Twomey20:10:41

Interesting - that’s good to know. I don’t think I need more than the basic let binding in this case, but I was unaware of this option, so thank you!

👍 1
Bob B20:10:52

(defn foo []
  (defn bar [x] x))

(bar 3)
;; Attempting to call unbound fn: #'bar

(foo)

(bar 3)
;; 3
it's generally frowned upon (fwiw): <https://guide.clojure.style/#dont-def-vars-inside-fns> as always, there might be cases where it makes sense, but if the desire is to scope replace-in-string to the containing fn, let or letfn is the best bet - if the goal is to make replace-in-string a namespace-level binding, defn it at the top-level

hiredman20:10:18

you can get much weirder more interesting errors depending on what you are doing with the value of the unbound var

hiredman20:10:24

% clj
Clojure 1.11.1
user=> (defn f [] (defn g []))
#'user/f
user=> (identity g)
#object[clojure.lang.Var$Unbound 0x4e558728 "Unbound: #'user/g"]
user=>

hiredman20:10:46

no error yet, just the special unbound value sentinel

Matthew Twomey20:10:51

Thanks all. The “plain” let binding will work fine for me. I didn’t really need anything more. I’ve just been experimenting with readability and different ways to do things. I liked that kondo gave that warning, it put me on this path to learn some new things. I’ve been also been putting partial through it paces for more general purpose functions where I want to sort of “pre-load” them with arguments and then run the fn against a map. I’m just getting more functional in my thinking and doing lots of tests.