beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
lw1990 2021-07-03T04:12:22.442800Z

Can someone help me understand why quote as a function is useful, why it exists, how often it's used?

2021-07-03T07:38:27.452300Z

Alright, so what is this:

"hello"
If you don't know programming, you'd tell me it's the word hello surrounded by double quotes. If you know programming you'd tell me it's a String whose value is hello But in actuality it's just a bunch of characters, they take on a meaning based on some contextual semantics. In the context of English reading, it is the word hello surrounded by double quotes. In the context of Clojure reading, it is a Unicode String whose value is hello. That's because characters surrounded by double quotes in Clojure mean that they represent a Unicode String. From this point on, lets remain in the realm of Clojure reading, so with that in mind, what is this:
:hello
It is an unqualified Keyword whose name is hello. That's because characters starting with a colon mean that they represent a Keyword. So what is this:
hello
This is a Symbol whose name is hello. That's because characters not prefixed or surrounded by anything special mean that they represent a Symbol. What is this:
("hello" "John")
This is a List with two Strings as elements. You know this because anything wrapped between open/close parenthesis represent a List in the context of reading Clojure. Now what is this:
(hello "John")
This is a List where the first element is a Symbol and the second a String. Now you might ask me, but isn't hello in this case a function? Well, no it is not, because I'm the context of reading Clojure, as we saw, this is a list of two elements, the first one is read as a Symbol, and the second as a String. But after you read source text in Clojure, you get back what you read. So after reading the text (hello "John" you now have a List of Symbol and String, this is no longer text, you hold now in memory an instance of a List object whose first element is an instance of a Symbol object and second element is an instance of a String object. Once you have an instance of a List, you can ask Clojure to evaluate this List as code. Now we are in the context of Clojure evaluation, and as you see, Clojure can evaluate a data-structure like a List, that's why people say in Clojure "code is data(structure)". Because code is what can be evaluated, and in Clojure that can be, among other things, a List data-structure. So if I were to represent this List in text once again, I would write (in the Clojure language):
(hello "John")
Now, after reading this and getting the List of Symbol and String, if I call eval on it, which Clojure will do automatically when given a source file to run, or when sending a piece of source to the REPL (where it will Read and then Eval), well what Clojure evaluation semantics will do is that they'll take the first element of any list and look it up to see if there is a function of that name and then replace it by that function, where the rest of the elements in the list will become the arguments to it. So when evaluating the List:
(hello "John")
Clojure will actually execute the hello function, now let's pretend the hello function is this:
(defn hello [name] (println "Hello " name))
That will return nil . So after evaluating our list, we get nil back. Okay, but now we have a problem, because we read a List, but as soon as Clojure evaluates it, it'll become a function (or macro) call. So what if we want the List itself? Imagine we had:
(def sum [numbers]
  (reduce + numbers))
And so I wanted to write:
(sum (1 2 3))
See the issue? When we read this, we see that we have a two element List where first element is a Symbol (sum) and second element another List. That nested List contains three Numbers. But now when Clojure evaluates this List, it'll consider sum a function, which is correct, and pass it as an argument the nested Lists, and prior to function executing, their arguments are also evaluated, so now we have a List being evaluated again, and Clojure will consider 1 to be a function and it'll call it with 2 and 3 as arguments, this will fail obviously. The problem is that since Lists represent function or macro calls when evaluated, how do you evaluate something to get a List back? And not have it be treated as a function or macro? That's where quote comes into the picture. You tell the Clojure compiler, the following form, please skip evaluating it, treat it simply for what it was read as. So now you can do:
(sum (quote (1 2 3)))
And Clojure will evaluate this where it'll consider sum a function, but this time it'll see that (1 2 3) is quoted, and so it won't evaluate it, it'll just take the List as-is and pass it to sum as the argument. It's a little meta, because of the homoiconicity, it makes it so that the textual representation for a List is the same as that for a function call, which means that you need a way to tell the compiler when it should be treated as a List or when it should be treated as a function call, and quote let's you do that, by saying don't evaluate what's quoted, treat them as-is the same as they were read.

2021-07-03T07:44:44.452500Z

Now because it's annoying to wrap the List in a List calling the quote special form, Clojure also has a reader macro character for it, and so everything that is prefixed by 'is read as if it was wrapped in a List calling quote.

lw1990 2021-07-03T04:12:30.443Z

https://clojuredocs.org/clojure.core/quote

lw1990 2021-07-03T04:16:17.443600Z

The only useful example I've found so far is when you want to print an expression out, like for debugging or something like that

indy 2021-07-03T04:31:51.447300Z

The single quote ' is just a macro reader that expands to quote . And these are what make "code as data" possible. Manipulating source code via macros wouldn't be possible without quoting. A lot of the usage of quoting is in macros where you're building and manipulating forms that are not going to be evaled "yet" (macroexpansion time). https://www.braveclojure.com/writing-macros/

2021-07-05T15:38:04.498600Z

it would be possible - just very inconvenient

(defmacro tedious-infix
  [& forms]
  (cons (symbol "do")
        (for [[x op y] forms]
          (list op x y))))

2021-07-05T15:38:16.498800Z

user=> (tedious-infix (1 + 1))
2

2021-07-05T15:38:43.499Z

(that's massively simplified in that it doesn't break down the x and y for subforms etc.)

dpsutton 2021-07-03T04:40:21.447800Z

I think what makes "code as data" is demonstrated well with (eval (list + 1 2))

💯 1
dpsutton 2021-07-03T04:41:54.449600Z

And think about what (list + 1 2) returns and how evaling a list can really do anything at all. if you're familiar with C# or Java, consider how weird it would be to eval(IEnumerable<Object>) or the like. And then recognize that (list + 1 2) is using just regular clojure functions and a regular clojure datastructure

dpsutton 2021-07-03T04:44:06.449800Z

(eval (list (quote let) (vector 'a 1) (list + (quote a) 2)))

lw1990 2021-07-03T04:52:05.451200Z

ok so apparently atom is not a special form, and the source code for it doesn't seem to use special forms directly, but I'm guessing it uses let or var somehow if you dig deep enough, is there a simple way to see this in a repl?

2021-07-03T07:45:37.452700Z

You should read this: https://clojure.org/reference/special_forms

2021-07-03T07:46:06.452900Z

I actually recommend you the read each section of the reference in order, it's pretty short, but it explains a lot of things really clearly.

2021-07-05T15:42:06.499700Z

@lukewallace1990 one thing you might be confused about is that clojure is not an interpreter - value storage is not implemented on top of let and var, it's done via java viirtual machine bytecode instructions

indy 2021-07-03T04:59:30.451300Z

It does use the new special form. An instance of the clojure.lang.Atom class will be created in this case.

seancorfield 2021-07-03T05:48:44.451700Z

@lukewallace1990 Can you see it in the REPL? Yes, via the source function:

user=> (source atom)
(defn atom
  "Creates and returns an Atom with an initial value of x and zero or
  more options (in any order):

  :meta metadata-map

  :validator validate-fn

  If metadata-map is supplied, it will become the metadata on the
  atom. validate-fn must be nil or a side-effect-free fn of one
  argument, which will be passed the intended new state on any state
  change. If the new state is unacceptable, the validate-fn should
  return false or throw an exception."
  {:added "1.0"
   :static true}
  ([x] (new clojure.lang.Atom x))
  ([x & options] (setup-reference (atom x) options)))
nil

seancorfield 2021-07-03T05:55:57.451900Z

Take a look at https://cljdoc.org/d/com.github.seancorfield/honeysql/2.0.0-rc3/doc/getting-started where you can use a DSL that involves either keywords or symbols: some people like the symbol-based version because they don't have to type : everywhere -- but you need ' so that the symbol names are treated as names and not looked up for their values.

Elias Elfarri 2021-07-03T11:02:18.454400Z

I am trying to use the defroutes macro to convert this:

(defn home-routes-backup
  (routes
    (GET "/" _
      (-> "public/index.html"
          io/resource
          io/input-stream
          response
          (assoc :headers {"Content-Type" "text/html; charset=utf-8"})))
    (resources "/")))
To this:
(defroutes home-routes
  (GET "/" [] (content-type (resource-response "index.html" {:root "public"}) "text/html"))
 (resources "/"))

Elias Elfarri 2021-07-03T11:03:21.455400Z

But i get a "java.lang.NullPointerException: Response map is nil" when i try to load up the localhost. Any ideas what could be the issue?? I tried to follow the tips from this thread https://stackoverflow.com/questions/7729628/serve-index-html-at-by-default-in-compojure

Elias Elfarri 2021-07-03T11:04:27.455900Z

These are the imports for clarity sake:

[compojure.route :refer [resources]]
[ring.util.response :refer [redirect response file-response resource-response content-type]]

bigos 2021-07-03T14:08:44.457Z

is it possible to use java source when compiling clojure jars to be used by java app? https://github.com/bigos/JavaApplication3/blob/master/src/clojure/responder/src/jac/responder.clj

bigos 2021-07-03T14:09:27.457600Z

ideally I would not only have the type of the object but also call it's methods

bigos 2021-07-03T14:14:04.457900Z

my build system is clj deps.edn

bigos 2021-07-03T14:28:57.458200Z

looks like it can not be done

bigos 2021-07-03T14:30:09.458700Z

😞

indy 2021-07-03T14:43:06.460200Z

Try adding the wrap-resource middleware https://ring-clojure.github.io/ring/ring.middleware.resource.html.

indy 2021-07-03T14:47:50.462300Z

The second argument to it being “public” if your files are in the resources/public folder

alexmiller 2021-07-03T15:04:45.463Z

the new tools.build library for deps.edn projects is nearly out and will support this

🎉 4
alexmiller 2021-07-03T15:06:18.463400Z

https://www.youtube.com/watch?v=BTAx-gFz6Ks

👏 5
1
1
dpsutton 2021-07-03T15:12:34.464800Z

Started watching that last night. The intro is hilarious. And whoever did the captions explaining the missing music really nailed it

alexmiller 2021-07-03T15:27:43.465Z

thx :)

alexmiller 2021-07-03T15:28:00.465200Z

it was better with the music though :)

dpsutton 2021-07-03T15:33:29.465400Z

i imagine. was glad to see it. looks amazing. thanks as always for your thoughtful work 🙂

Juλian (he/him) 2021-07-03T19:00:39.466500Z

is there a way to set environment variables? more specific: can I have clojure load environment variables from an .env file, at least for when I use the repl?

2021-07-05T15:44:14.499900Z

btw not being able to manage env vars at runtime is a portability issue, and via the right OS system calls it can be done, you just give up on the one very popular OS where this can't be done

2021-07-05T15:45:45Z

also the 12 factor thing of providing config via environment makes stealing credentials very easy one linux (the OS almost everyone actually uses in prod) - just slurp /proc/$PID/environ and you get the whole thing or better yet, the shortcut /proc/self/environ since the kernel knows your PID

👍 1
2021-07-05T16:59:09.010900Z

Even if you change the env vars through the OS it won't reflect in Java, and System.getenv will return only what was there at startup, because Java caches the environment on startup I believe. I saw some ways to change the JVM env cache, but it's not an official API, so depending on your JVM and its version it might not work. I think other langs, when you execute setenv it doesn't actually set the environment vars of the parent, but simply changes the one of the current process, that bit I'm not sure why Java doesn't allow it.

2021-07-05T18:35:47.036200Z

it's exactly the "set the vars of the current process" bit that isn't portable though

2021-07-05T18:36:12.036400Z

if you need a child to have a different env, ProcessBuilder supports that, but that happens in a full new process not the current vm

Juλian (he/him) 2021-07-03T19:16:51.467800Z

maybe I should drop the environment variable idea and use config files instead... outpace/config looks promising for that purpose

rickheere 2021-07-03T19:27:44.475700Z

Some ClojureScript

(defn get-prices-and-balances
  [client return]
  (let [prices-c (chan 1 (map #(assoc {} :prices %)))
        balances-c (chan 1 (map #(assoc {} :balances %)))
        combined-c (merge [prices-c balances-c])]
    (b/get-all-prices client prices-c)
    (b/get-balances client balances-c)
    (go-loop [prices nil
              balances nil]
      (if (some nil? [prices balances])
        (let [result (<! combined-c)]
          (cond
            (:prices result) (recur (:prices result) balances)
            (:balances result) (recur prices (:balances result))))
        (>! return
            (assoc {} :prices prices :balances balances))))))
The question I have is. Is there another way to fire off 2 async calls (get data from 2 resources on the internet in this case) and manage the data returned. I know I can do better in this code with putting the values on a map, then taking them from the map and putting it in the recur and all the way at the end put it on a map again. The thing is also, both the b/get-all-prices and b/get-balances functions internally return promises that I put on core.async chancels but now I'm starting to get the idea it would be better to just leave those as promises and use something like (<p! (.all js/Promise #js [b/get-all-prices b/get-balances])) and I get the same effect (parallel calles). I also made something with pipeline-async in the past but ended up with quite some code as well. I have the idea I'm missing something.

2021-07-03T19:35:48.476100Z

Or use into instead of merge

2021-07-03T19:36:03.476600Z

Or don't combine the channels at all and loop around alt

dpsutton 2021-07-03T19:38:47.477500Z

what benefit do you get from all of this rather than

(defn ps-and-bs [client]
  (go
    (let [p (b/get-all-prices client)
          b (b/get-balances client)]
      {:prices (<! p-c )
       :balances (<! b-c)})))
Return a channel rather than taking in a channel. you can pipe from one to the other though if you want. No adding things into maps and then taking out and then adding back into a map

👌 1
1
rickheere 2021-07-03T19:45:41.480Z

This is gold, I did miss that a go block returns a channel with the result, and yes I can just wait for both to resolve. This is exactly what I wanted. I just thought I needed all that other code to get this effect. Thanks a bunch!!

dpsutton 2021-07-03T19:46:37.481400Z

Yeah just fire the events before waiting on them achieves what you want

rickheere 2021-07-03T19:47:25.481900Z

I read, I think in a book form Alex Miller it would be more flexible to accept channels instead of creating and returning them.

rickheere 2021-07-03T19:48:12.482800Z

Thats why I did that. But then again, this is not some library but just my own code so yeah, this is easier anyway

indy 2021-07-03T19:55:59.483Z

Also check out https://github.com/juxt/aero. Also hand rolling config accessors is pretty straight forward. 1. Read from edn. 2. Store it in an atom. 3. Have functions accessors that when called get the values from the atom

rickheere 2021-07-03T19:56:04.483500Z

Thanks for the reply as well @hiredman

Juλian (he/him) 2021-07-03T20:17:35.483800Z

thanks

seancorfield 2021-07-03T20:24:58.484Z

You cannot set environment variables once a process has started (at all). You can set system properties in the JVM however, so properties are a bit more flexible than environment variables in that respect. But using an external config file is probably a better approach.

2021-07-03T20:44:58.484300Z

The environment variables as the name implies are managed by the "environment". So what you need to do is set them in your environment before launching your REPL, and then you can read them from the REPL.

2021-07-03T20:49:24.484500Z

If you use lein to start your REPL, I've used: https://github.com/athos/lein-with-env-vars before to have lein define some environment variables to set.

2021-07-03T21:15:39.484800Z

You can also have a look at: https://github.com/cdimascio/dotenv-java which attempts to replicate Ruby's dotenv. But like Sean said, JVM has immutable-ish env variables, so you can't set them once the program is running. The dotenv-java bypasses this by having you use its own API to get env variables. What I'd recommend though is to use System/getProperty to get the your .env config, and use dotenv-java with its systemProperties option. In general, Java libs that look for env variables have logic to fallback to properties as well (or vice versa).

2021-07-03T21:25:34.485200Z

Now, I personally strongly disagree with the 12 factor app idea of using env variables for config. In my experience, it creates a mess. I recommend going with configuration for your app. And what I do is that I have my configuration be defined per-environment. Now the only environment variable I have is the name of the environment I'm in. I then use that to load the right config. Imagine: default.edn johnny-desktop.edn bob-desktop.edn build-server.edn ci.edn beta.edn prod-eu.edn prod-na.edn And then an env variable: APP_ENVIRONMENT=beta

2021-07-03T21:27:04.485400Z

That way, configuration is still in code, you have it versioned controlled, can roll it back, know exactly what the config was at a point in time, etc

2021-07-03T21:34:39.485600Z

Here's a nice library that implements something similar to what I'm recommending: https://github.com/levand/immuconf