Fork me on GitHub
#beginners
<
2018-03-23
>
edward01:03:35

Hello, I am trying to make a synchronous http request with clojurescript. I tried installing clj-http but got the following error

Failed to compile build :app from ["src" "env/dev/cljs"] in 14.445 seconds.
----  Could not Analyze  src/myproject/core.cljs  ----

  No such namespace: clj-http.client, could not locate clj_http/client.cljs, clj_http/client.cljc, or JavaScript source providing "clj-http.client" 
I think this is because clj-http is clojure instead of clojurescript? Here is my project.clj
...
:dependencies [clj-http "3.8.0"]
...
Any help would be appreciated!

edward01:03:36

The error is coming from the line (:require [clj-http.client :as client]) in my core.cljs

grounded_sage01:03:16

Is there a core function for repeating a function n times on a literal. Eg I want to multiply a number n times by another number.

edward02:03:21

@seancorfield thanks for the link. However it doesn't look like cljs-http does synchronous http requests. What I want to do is a make a request and then play around with the result outside a (go ..) block

jumar08:03:18

@edward.banner you might want to try https://github.com/JulianBirch/cljs-ajax Luminus template might be a good start for creating a simple clojure+clojurescript application. See http://www.luminusweb.net/docs/profiles.md I think that +re-frame will automatically gives you a little example using cljs-ajax although it might be a bit of overkill for you.

edward02:03:35

That doesn't appear to be possible with cljs-http...

seancorfield02:03:47

Well, that's JS for you -- it's single-threaded so you need to collaborate with the runtime on any blocking operations.

edward02:03:57

Got it. Well do you have any suggestions for accessing a variable outside a (go ..) block then? https://stackoverflow.com/questions/36630320/get-information-out-of-go-block looks relevant but there was no clear solution there

edward02:03:51

I thought something like

(go (let [response (<! (http/get ""))]
      (def R response)))
would work and I would be able to play around with R. But it didn't work

seancorfield02:03:52

Sorry, I don't use cljs -- and on the JVM you've got threads so you can do blocking takes from channels in different threads...

seancorfield02:03:23

def always defines a global top-level var -- you should not use that inside functions.

edward02:03:37

I think it's OK for now. I'm just playing around in a REPL

seancorfield02:03:06

In JS, you're pretty much required to use async code to work around the lack of threading -- so you're going to have to use core.async and go blocks and structure your code accordingly, as far as I know.

edward02:03:22

Wow ok thanks. I'm new to javascript. So there's no way even in javascript to set a global variable with the response in a callback...?

seancorfield02:03:02

I don't program in JS at all.

seancorfield02:03:15

(and this sort of stuff is mostly why)

edward02:03:38

OK thanks for all your help!

seancorfield02:03:13

Sorry, I couldn't be more help. You could try the #clojurescript channel or #core-async to see if they have more useful advice...

justinlee02:03:41

@edward.banner you can definitely set an atom in a callback if you are just playing around in a repl

justinlee02:03:38

the mistake most people make is that they run an async operation and then try to read that atom immediately in the code below it and it will never be set because the async operation doesn’t run until you relinquish control back to the event loop

justinlee02:03:26

i don’t really know how go blocks work in a repl, however, because they are big complicated macros that compile down to a state machine. clojurescript repls work in mysterious ways

justinlee02:03:46

try

(def r (atom nil))
(go (let [response (<! (http/get ""))]
      (reset! r response)))
then evaluate @r in the repl

edward02:03:51

Yeah for some reason it doesn't work. It works fine when I do a (pr response) in the go block but not when I do a reset! nor def

justinlee02:03:38

and you’re executing all of this in a clojurescript repl?

edward02:03:46

Yes. I'm using cider

justinlee02:03:55

connected to a browser?

edward02:03:04

(go (let [response (<! (http/get "http://localhost:8000/lucky/foo"))]
      (pr response)))
> {:status 200, :success true, :body {:foo "bar"}, :headers {"content-type" "application/json"}, :trace-redirects ["" ""], :error-code :no-error, :error-text ""}

justinlee02:03:39

and the atom still evaluates to nil after you reset! it?

justinlee02:03:45

that is unexpected to me

edward02:03:11

(def r (atom nil))
(go (let [response (<! (http/get "http://localhost:8000/lucky/foo"))]
      (reset! r response)))
> {:status 0, :success false, :body "", :headers {}, :trace-redirects ["" ""], :error-code :http-error, :error-text " [0]"}

edward02:03:03

No that atom is not nil anymore

edward02:03:15

But the request is unsuccessful

justinlee02:03:31

now that is perplexing. the only time i’ve ever seen a :status 0 return code is on a CORS issue. i cannot fathom how changing the body of the let block could change the result.

edward02:03:18

Ah I was debugging cors recently...

justinlee02:03:05

@edward.banner what happens when you do both the print and the reset?

edward02:03:32

Huh I'm getting weird errors now. I can't confirm that they aren't causing the weird behavior I am seeing now so I'll try and fix them first

edward02:03:17

Errors like

base.js:677 goog.require could not find: cljs.tools.reader.impl.errors
goog.logToConsole_ @ base.js:677
goog.require @ base.js:709
(anonymous) @ commons.cljs?rel=1521772505217:9
base.js:711 Uncaught Error: goog.require could not find: cljs.tools.reader.impl.errors
    at Object.goog.require (base.js:711)
    at commons.cljs?rel=1521772505217:9
goog.require @ base.js:711
(anonymous) @ commons.cljs?rel=1521772505217:9
base.js:677 goog.require could not find: cljs.tools.reader.impl.errors
goog.logToConsole_ @ base.js:677
goog.require @ base.js:709
(anonymous) @ reader.cljs?rel=1521772505574:9
base.js:711 Uncaught Error: goog.require could not find: cljs.tools.reader.impl.errors
    at Object.goog.require (base.js:711)
    at reader.cljs?rel=1521772505574:9
goog.require @ base.js:711
(anonymous) @ reader.cljs?rel=1521772505574:9
base.js:677 goog.require could not find: cljs.tools.reader.impl.errors
goog.logToConsole_ @ base.js:677
goog.require @ base.js:709
(anonymous) @ edn.cljs?rel=1521772505758:9
base.js:711 Uncaught Error: goog.require could not find: cljs.tools.reader.impl.errors
    at Object.goog.require (base.js:711)
    at edn.cljs?rel=1521772505758:9

justinlee02:03:46

you’ve angered the repl

justinlee02:03:53

sorry i don’t know what that is

justinlee02:03:40

if you have hot reloading going (e.g. figwheel) it may be easier to do this in a file and have it reload on save. the problem with debugging a repl sometimes is that you start accumulating a bunch of state

edward02:03:55

Yeah I am using figwheel haha

justinlee02:03:19

i mean just stick this stuff in a file and print to console. it’s almost as good as a repl 🙂

edward02:03:21

Sorry I failed to mention that I am using both

edward18:03:31

Hey @U8ES68TGX it turns out that the error stemmed from a configuration-related issue. Not sure what it was but setting a global variable inside the go loop works now!

justinlee18:03:58

Great! You were making me nervous because that should have worked just fine.

justinlee18:03:34

Of course, in real code you do not want to do that because it is unreliable and you’ll create a race condition. You’ll want to wait on the channel that go returns

edward18:03:55

Right! Got it

grounded_sage02:03:49

How do I multiply a float?

mfikes02:03:12

@grounded_sage What are you attempting to accomplish? Do you need a float result or is double OK?

grounded_sage02:03:51

All good. It was my code. I was trying to multiply a string :rolling_on_the_floor_laughing:

Michael Fiano04:03:53

@seancorfield Do you know how I can create a boot template from an existing project?

seancorfield05:03:15

@mfiano No, not directly I'm afraid. That's a very interesting idea tho'... A "reverse template generator"...

Michael Fiano05:03:53

Ok, I was just curious, because I have a bit of work into a project organization that I'd like future projects to follow.

seancorfield06:03:35

I guess you'd want a way to walk the directory structure and generate the template code from it, and then you could go in and edit the files to put the "variables" into it all.

seancorfield06:03:55

Go open a ticket against the boot-new project and I'll have a think about it.

matan08:03:57

does slamhound fix up namespace paths in require and ns expressions for you, if you e.g. rename a source folder?

matan08:03:26

in my quick experiment it does not, admiteddly as a bad habit I really like renaming things on the go as my code evolves

Nikos10:03:42

Emacs-related: is there a way to make the repl print each element of a sequence on a new line? I'm using Brave and True to learn Clojure and the example outputs are so nicely formatted. I was wondering if I can achieve the same result.

agile_geek10:03:24

If you are using cider in emacs for your repl you can add this to you init.el (setq cider-repl-use-pretty-printing t) which should pretty print any results in the repl

💯 4
Nikos11:03:01

Thanks! I've already tried toggling that option on and off but it didn't seem to have an effect.

Nikos11:03:58

Wait a second, it is working now. Nevermind my previous comment, and thanks again!

dpsutton12:03:13

Nikos press comma on a blank line in the repl and it will bring up a menu. Lots of good things there but you'll see the pretty printer option and you can just select it and press enter

michaels14:03:49

I’m trying once more to work my way through modern-cljs. I’m 2/3 through tutorial 1 - and getting

clojure.lang.ExceptionInfo: Call to clojure.core/ns did not conform to spec:
                                         In: [1] val: ((require [clojure.string :as string] [cljs.source-map.base64 :as base64])) fails spec: :clojure.core.specs.alpha/ns-form at: [:args] predicate: (cat :docstring (? string?) :attr-map (? map?) :clauses :clojure.core.specs.alpha/ns-clauses),  Extra input
What should this point me to? I only have core.cljs and build.boot as files. core.cljs starts with (ns modern-cljs.core)

dpsutton15:03:10

in the ns form you want to use :require (note keyword) not require

michaels15:03:09

There is no require in my file; it must be in my only dependency, boot-cljs.

Alex Miller (Clojure team)15:03:02

I think that was actually in cljs but was fixed long ago

michaels15:03:38

I’m not specifying a version of clojure or clojurescript - I’m not sure how to determine what the defaults are that are being brought in.

Alex Miller (Clojure team)15:03:39

yeah, this was in cljs and fixed in version 1.9.198

michaels15:03:35

just dropped dependencies for clojurescript and clojure into build.boot - that fixed it.

michaels15:03:40

Thanks folks. 🙂

shakdwipeea16:03:13

What is a good rule of thumb to have for arguments to a function ? Should they be 1. positional arguments 2. keyword arguments 3. single argument which is a map which can contain the values as a key and can then be destructed in the defn.

shakdwipeea16:03:10

Any pointers to resource where I can study about these kind designs would be great.

schmee16:03:08

the one tip I have is to use maps instead of keyword arguments

schmee16:03:18

maps are easier to merge, easier to use with apply etc.

shakdwipeea16:03:22

@schmee I was thinking along the same lines, so what would be a tipping point in favour of map ... for example if I am dealing with two values even if they are related sometimes I tend to use them as different positional arguments. however if they grow say more than 3 then I tend to use maps. I know that this is highly dependent on context but still is this a good default ?.

mfikes16:03:07

@shakdwipeea One common pattern that seems to be a good compromise is to have positional arguments for the common call use case, but a last argument that is a map, usually called opts to carry optional arguments that "fine tune" the base behavior. Another common pattern is to introduce a "positional arity" that delegates to the one with the map arg, passing nil for the map.

mfikes16:03:27

The "resource" to study, in my example is the ClojureScript compiler codebase.

shakdwipeea16:03:46

Thanks, this really clarifies a lot of my doubts.

mfikes16:03:51

If I had to guess, that pattern isn't used that prevalently. Most functions take positional arguments, but there are a class of them (maybe 10% or 20% that thread opts through). It makes for a nice escape hatch when you need it.

mfikes16:03:53

Also, as John alluded to, it appears that keyword arguments was an experiment that fell out of favor.

mfikes16:03:21

I think the one place where I've seen that (keyword args) might make sense is when humans are typing in stuff to run things. For example ClojureScript REPL startup code employs this pattern in places, so you can just tack on :repl-verbose true to the argument list, for exmple

shakdwipeea16:03:25

This pattern actually fits perfectly to my use case . I had to pass some extra options for running some pipelines so was looking around for ways to do it.

shakdwipeea16:03:15

This way the common params could be the positional params and the options for configuring the behaviour could go in a opts map.

mfikes16:03:50

Yeah, and it seems that you usually have a good feel for what is "required" vs. "optional"

mfikes17:03:17

The "threading" aspect of an opts map starts to shine if you have a few layers in the call stack, and you just want to pass something down through it without changing any signatures.

mfikes17:03:03

I suppose that case can feel like a dynamic var, but with things being explicitly passed.

shakdwipeea17:03:54

this is just amazing, I had not thought of it this way so I always had difficulty chaining together a common set of functionality. This way new functionalities could be added to the processing chain and those could be controlled by just tweaking the map being passed.

mfikes17:03:03

This also fits in with the mentality that Spec provides a mechanism to indicate that "if this key is in the map, then its value satisfies this predicate" but it doesn't provide a mechanism that would allow you to limit which keys go into the map.

shakdwipeea17:03:02

I had seen a discussion about that, makes sense.

mfikes17:03:09

If you later decide a "required" argument is optional, you can also handle that fairly easily. For example: https://github.com/clojure/clojurescript/commit/3a6e71e4bb01f97c7a86f46bfed003a602ed5ad3

👍 4
Michael Fiano19:03:00

What is the idiomatic Clojure way to create a record with default field values?

john19:03:19

I usually see a second constructor sort of function which applies default values to the new object

Michael Fiano19:03:49

Ah so something with merge like you would do with a regular hashmap?

john19:03:13

The second function becomes your entry point and the first you name thing`-impl` or what have you. Yes exactly

Michael Fiano19:03:54

This is what I came up with. This is also my first Clojure code written. How well is this?

(defrecord MapCell [x y carved? region features distance])

(def cell-defaults
  {:carved? false
   :features []
   :distance -1})

(defn make-cell
  [x y]
  (map->MapCell (merge cell-defaults {:x x, :y y})))

Alex Miller (Clojure team)20:03:06

that’s fine. or just (defn make-cell [x y] (->MapCell x y false nil [] -1)) ?

john20:03:14

that's actually spread across three functions

Michael Fiano20:03:52

Ok. I'll probably make a macro because I can see this pattern used over and over again. I'm surprised there is nothing standard to make this a bit more concise.

greglook20:03:34

I usually make an intentional constructor function that is responsible for things like defaults and enforcing invariants on the required parameters, then privatize the automatic constructors to prevent people from using them unawares.

Michael Fiano20:03:04

Can you give an example?

greglook20:03:44

sure, this is one I use in a bunch of places:

(defmacro privatize-constructors!
  "Changes the default record constructor functions into private vars."
  [type-name]
  `(do (alter-meta! (var ~(symbol (str "->" type-name))) assoc :private true)
       (alter-meta! (var ~(symbol (str "map->" type-name))) assoc :private true)))

greglook20:03:10

so then you’d:

(defrecord Foo [a b x y])

(privatize-constructors! Foo)

(defn make-foo
  [a b opts]
  {:pre [(string? a) (some? b)]}
  (map->Foo (merge {:x 5, :y true} opts {:a a, :b b})))

greglook20:03:42

now all callers outside of the namespace who want to construct new foo instances must use make-foo, or fall back to Java interop. Nothing stops someone from changing the record once constructed, of course - so you could still have non-string values in a if you do (assoc foo :a 123)

greglook20:03:01

but it does catch problems at construction time and provide a common place for defaults

Michael Fiano20:03:50

I wasn't familiar with alter-meta! or realized things have metadata

Michael Fiano20:03:02

This causes me to think a bit 🙂

greglook20:03:13

ah, yeah - that’s where stuff like the docstring is kept as well (on vars)

Michael Fiano20:03:38

Is that the hashmap for like pre/post conditions too?

greglook20:03:10

indeed - you could do a more verbose version like (when-not (string? a) (throw (IllegalArgumentException. "Argument 'a' must be a string!")))

greglook20:03:36

which you might want to do in some cases, since preconditions can be disabled and also throw AssertionError, which is not super friendly to callers

greglook20:03:22

if you want more metadata examples, try (meta #'+) in a repl 🙂

Michael Fiano20:03:31

That's great. So the metadata is a hashmap before the function body for functions? and you use alter-meta! for other forms?

greglook20:03:58

not exactly - that map above is only for pre and post conditions. You can attach arbitrary metadata in a def or defn by attaching it to the symbol though, which is why these are equivalent:

(defn- do-thing
  "Does a thing"
  [x]
  (println x))

(defn ^:private do-thing
  "Does a thing"
  [x]
  (println x))

; fully expanded:
(def ^{:private true, :doc "Does a thing"} do-thing
  (fn [x]
    (println x)))

sundarj20:03:16

you can attach metadata to functions in 3 places

greglook20:03:16

some hand waving, but hopefully gets the idea across

sundarj20:03:24

=> (defn ^{:foo 2 :bar 3} foo {:baz 4 :qux 5} ([] 42) {:fred 6 :wilma 7})
#'boot.user/foo
=> (meta #'foo)
{:baz 4, :fred 6, :bar 3, :ns #object[clojure.lang.Namespace 0xab8dcf8 "boot.user"], :name foo, :file "NO_SOURCE_FILE", :wilma 7, :column 1, :line 1, :qux 5, :foo 2, :arglists ([])}

greglook20:03:48

huh, didn’t know about the tail position when you are wrapping arities

sundarj20:03:09

it's so if you have a really big metadata map, you can put it after the bodies

Michael Fiano20:03:40

Clojure keeps impressing me. Thanks for all the explanation

john20:03:46

note that metadata aren't carried around with vars in cljs at runtime, like they are in clj

Michael Fiano20:03:24

Nice to know. I haven't explored cljs yet

Michael Fiano20:03:28

Thank you all!

lambdalove 4
brunobraga23:03:38

hey everyone, I found out a clojure boilerplate project that has a section for tests...right now it is the following

brunobraga23:03:51

(ns my-stuff.core-test
  (:require [clojure.test :refer :all]
            [my-stuff.core :refer :all]))

(deftest a-test
  (testing "FIXME, I fail."
    (is (= 1 1))))

brunobraga23:03:46

does someone have an example of how I can import my functions from core.clj inside src/my_stuff/core.clj so that I can test it

schmee23:03:11

those functions are already imported, that's what [my-stuff.core :refer :all] does

schmee23:03:37

so if you have a function my-stuff/foo you can just use it as foo in the test namespace

brunobraga23:03:43

yeeah makes sense, I got it just now.

brunobraga23:03:57

is it possible to run tests in a more verbose manner? with logs and such