Fork me on GitHub
#beginners
<
2020-02-20
>
happyb3at02:02:52

[:app] Configuring build. [:app] Compiling ... To quit, type: :cljs/quit [:selected :app][:app] Build completed. (551 files, 1 compiled, 0 warnings, 6,73s) No application has connected to the REPL server. Make sure your JS environment has loaded your compiled ClojureScript code. Happens right after starting emacs and issuing cider-jack-in on a project that works fine and just fidling around in a .cljs file. So weird.

happyb3at02:02:40

neither emacs restart or os boot fixes the issue

dpsutton02:02:29

That’s a cljs repl starting up. It’s basically a jvm program that evaluates your cljs output in a JavaScript engine. It’s saying all is ready but you need to connect to a JavaScript engine. Usually by loading your app in a browser or running node on your generated cljs script

Michael Stokley03:02:23

this is a silly question but back in java-land, i'm used to writing objects with methods that refer to some state that object was originally initialized with. an example might be a client that requires some broad configuration and then exposes a few methods. is there a comparable pattern in clojure?

Alex Miller (Clojure team)04:02:31

you can close over state when you construct a function or reify an interface

Michael Stokley04:02:05

like this?

(defn f1 [a b c d] ...)
(defn f2 [a b c d e] ...)

(defn make-client [a b c]
  {:f1 (partial f1 a b c)
   :f2 (partial f2 a b c)})

Michael Stokley04:02:14

that's just a naive guess - would something like that be idiomatic?

didibus04:02:01

Ok, sorry, said yup and got distracted

didibus04:02:17

A common pattern is to do this:

didibus04:02:46

(defn make-client [option1 opt2 opt3 ...]
  {:option1 option1
   :option2 opt2
   ...})

(defn get-items [client-map some-other-arg ...]
  ...)

didibus04:02:56

So say you had the Java class:

public class Client {
  private int x;
  private int y;

  public Client(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public method1() {...}
  public method2() {...}
}

Michael Stokley04:02:57

the "client" here is just a hash-map that groups the common args, looks like

didibus04:02:40

So the above class would become:

(defn make-client [x y]
  {:x x
   :y y})

(defn method1 [client] ...)
(defn method2 [client] ...)

Michael Stokley04:02:43

that's too simple

Michael Stokley04:02:55

i love it. thank you!

didibus04:02:45

And the difference will be that, the map is immutable. So your defns are not really methods anymore, because methods are assumed to mutate the fields. In our case, if you only need to read from the fields, you just read what you need from the map. But if you need to modify the fields, you would instead return a new map which is the given client map with whatever changes applied to it.

Michael Stokley04:02:32

i feel like another important difference is that we've mostly eliminated the abstraction that is a stateful "client"

Michael Stokley04:02:47

the fact that it's still called a 'client' notwithstanding

didibus04:02:13

Ya, it depends. If the things in your client map allow you to make a remote call to a mutable database, there's still some state in that sense, but it's moved away from your "client" object, and is now where it should be, in your DB for example

didibus04:02:26

And what's nice with this, is you can now call it like so:

(-> (make-client 10 20) (method1) (method2))

didibus04:02:14

Given in this case your methods were to return back a client-map as well.

didibus04:02:36

Which is often the case. Like everything that would be void in java, in Clojure would instead return the client-map

didibus04:02:16

So you get fluent interfaces for free

didibus04:02:23

And if you want to group the client and all its methods into one logical place, then just create a namespace for them and shove it all there 😛

💯 4
didibus04:02:47

This pattern can extend quite far, if you ever need some polymorphism, you can make the map a record, and put the functions inside a protocol. Or keep it a map, but modify some of the methods into multi-methods, etc. Lots of options for handling all possible scenarios

didibus04:02:06

If people find they are confused about what a client contains, you can create a spec for it. And on and on...

Michael Stokley04:02:19

thank you again, this is great

didibus04:02:23

No problem. It can be hard in the beginning to peel the onion. I came from OOP as well, and I think your question is just normal, everyone who does has it, we're almost too used to complexity 😛, that we find it hard to think without it

Michael Stokley04:02:30

really, for me, the worst part is that the concept of a "client", as an object with state and behavior, is accomplishing nothing. the pattern of simply grouping together the data that happens to be shared gets you all the practicality and none of the useless abstraction

👍 8
hindol05:02:38

@U0K064KQV I was going through this thread as I am in general interested in patterns. About treating a namespace as a poor man's class, do you actually use it in production code? I think the aspect of multiple instances of client class with independent states is lost with this approach. All you have is a global shared state.

jumpnbrownweasel05:02:11

@UJRDALZA5, if I understand the thread, I think that would be true if there were defs in the namespace containing the state. But I don't think he's suggesting that. Rather a map contains the state and is passed to each fn. So there can be any number of maps (OO instances).

👍 4
hindol05:02:36

You are right! I assumed the all in "shove it all there" included state.

didibus06:02:19

Ya, @UBRMX7MT7 is correct. The pattern here is that "make-client" acts as a constructor for instances of your data.

👍 4
Mattias06:02:04

This is really interesting to me. Not really seeing the difference between getting values (“state”) from a function vs. from a variable of some kind. In both cases it’s a symbol that evaluates to values. Help? 😳

didibus06:02:09

But, some use cases in OOP make use of static fields. And in clojure, those would just be global defs

didibus06:02:41

@UCW3QKWKT Hum... imagine you have a Contacts app where the user can create new contacts.

didibus06:02:33

You can't just do:

(def contact
  {:name "Joe" :phone 2223345})

didibus06:02:58

Because, the user will be adding contact dynamically, at runtime. They could add from 0 to n of them, who knows

didibus06:02:15

And you can't predict what name or phone the contacts are going to have. So what you need instead is a way to both parameterize your contact map, but also possibly create many of them, all with different names and phones

didibus06:02:59

You can do that with a function

didibus06:02:36

(defn make-contact [name phone]
  {:name name :phone phone})

didibus06:02:50

So now when the user press "Add contact" and enters a name and phone on the form and clicks submit, you can use make-client to dynamically create a new instance of a contact

didibus06:02:07

Does that make sense?

didibus06:02:14

With a def, you need to know the values ahead of time, and how many unique contact you'll have.

didibus06:02:58

But with a defn, you don't, you can wait till runtime and then use it to create as many of them with whatever values as you want.

hindol06:02:10

But we can have a (def contacts (atom [])) . For any non-trivial apps, I think there will always be a giant state map anchored to some namespace somewhere.

didibus06:02:35

You often don't need one actually

didibus06:02:36

A lot of apps will store the states straight into a DB of some sort. And some others can just carry around state through from start to finish, including long lived apps by using recursion

didibus06:02:39

But yes, some will. But that would be in the containing component.

hindol06:02:48

DB is just an extension of what I said. The data has to be anchored to something. But I am very curious about the other thing you said.

hindol06:02:12

And some others can just carry around state through from start to finish, including long lived apps by using recursion Please expand on it if you have the time.

didibus06:02:38

For example:

(def contacts (atom []))

(defn on-new-contact [name phone]
  (swap! contacts conj (make-contact name phone)))

hindol06:02:13

But a def is still anchored. I thought there's a clever trick here.

didibus06:02:04

Sorry, my example in slack was for: "But yes, some will. But that would be in the containing component."

😀 4
didibus06:02:25

The link to clojureverse shows how to carry over state

hindol06:02:32

Guess I am typing too quickly.

didibus06:02:59

And me too slowly 😋, doing all this from a phone is hard

didibus06:02:46

With the functional approach, there are even some DBs that are implemented like that. If you ever heard of a log store, or an append only store. Basically, you keep carrying last state and making new states out of it, never mutating, and once in a while you just delete the history you no longer need to save on memory/space.

hindol07:02:37

Nice explanation there in the linked article! You can encapsulate even more by passing a make-board fn in there instead of the init-board. But you might already be doing that, since you only shared the loop/recur part.

didibus07:02:18

Yup, definitly. If your initial board needs to be parameterized that be a good idea. Like say on "New Game" you took some options, type of game, size of board, who knows

Michael Stokley05:02:43

is there a naming convention to indicate that a given var might be nil or absent?

hiredman06:02:33

A point of terminology, a var is something created by a def, a global definition, those are pretty much never nil

hiredman06:02:28

A name bound by let or a function in clojure is called a local, and those are indeed sometimes nil

👍 4
Michael Stokley05:02:37

in scala we're always so explicit about values we expect to always be present and values we might reasonably expect to be missing - does lisp/clojure have a similar approach?

didibus06:02:34

So, the way its done in Clojure, is that absence is represented by the key being missing from the map

didibus06:02:01

You can do that, because of how dynamic the data constructs are

didibus06:02:24

So you have two concepts. First is the data is missing, that would be represented as the key isn't even there. The next one is, the data is there, but its value is nil. That would be represented with the key being there but having a value of nil

didibus06:02:59

So say you had person maps, and sometimes those have a name and an address, sometimes only an address.

didibus06:02:44

You would have:

{:name "Joe" :address "123 bla"}
And if its a person without an address:
{:name "Joe"}
And if its a person where an address was given, but it was garbage, or it was submitted as blank, or the user ticked the box saying "rather not say" you'd have:
{:name "Joe" :address nil}

didibus07:02:01

Oh, and to specify the semantics of a particular map, Spec would be used. You can say which key are mandatory, which are optional, and which are allowed to be nil or not

didibus07:02:04

Though sometimes, simply doc or comments are used, or even its just not specified if its kind of obvious from context

jumpnbrownweasel05:02:14

@michael740, I have not seen any such convention in Clojure code.

bartuka13:02:46

I have a failing test inside a cljc file, when I run lein test it does not catch the error. But in the console I see that the file was tested. I should do something different in the setup of clojure.test for this situation?

seancorfield13:02:19

@iagwanderson Not sure I'm following you -- but it seemed to work when I tried it

(! 779)-> lein new app bartuka
Generating a project called bartuka based on the 'app' template.
(! 780)-> cd bartuka/
(! 781)-> mv src/bartuka/core.clj src/bartuka/core.cljc
(! 782)-> mv test/bartuka/core_test.clj test/bartuka/core_test.cljc
(! 783)-> lein test
lein test bartuka.core-test

lein test :only bartuka.core-test/a-test

FAIL in (a-test) (core_test.cljc:7)
FIXME, I fail.
expected: (= 0 1)
  actual: (not (= 0 1))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
Tests failed.
(! 784)-> echo $?
1
(! 785)-> 

bartuka13:02:40

that's exactly the problem. But, if yours is working fine, it should be a problem with my current setup

bartuka13:02:14

I have added :test-paths ["test/cljc", "test/clj"] to my project.clj and nothing else 😕

dpsutton14:02:28

how do you know it is failing? Is it possible only a cljs branch fails and you aren't running tests in that environment?

bartuka14:02:28

found it! Look at the next example, when I use the use-fixtures it passes with 0 failures.

(ns mamulengo.bartuka
  #?@(:clj
      [(:require [clojure.test :refer [deftest is use-fixtures]])]
      :cljs
      [(:require [cljs.test :refer-macros [deftest is use-fixtures]])]))

(use-fixtures
  :each
  {:before (fn [] "ok")
   :after (fn [] "go")})


(deftest please-work!
  (is (= 0 1)))

bartuka14:02:07

the api of use-fixtures is different for both environments. I would expect some error.

bartuka14:02:24

actually, it seems like the test was not even recognized, which makes sense because it breaks before. Only hard to catch

seancorfield14:02:48

Ah, yes, the Clojure code -- run by lein test -- would try to invoke that hash map as a function, passing in the test to be executed.

seancorfield14:02:37

so it would run ({:before,,,} please-work!) which will "run" but not call please-work!

seancorfield14:02:04

because that's (get {:before,,,} please-work!) which just returns nil

seancorfield14:02:59

I'm surprised use-fixtures differs between Clojure and cljs like that...

bartuka14:02:15

yes, right? This one was tricky.

bartuka14:02:42

thanks for the help!

seancorfield14:02:28

If you write a function that accepts a test function as an argument (and calls it), that will work for both clj and cljs.

seancorfield14:02:34

Supporting that :before/`:after` format would be a nice enhancement for clojure.test.

clj 4
bartuka14:02:50

I like how readable this approach is

seancorfield15:02:53

I'll go ahead and implement this in expectations.clojure.test tho'...

dpsutton14:02:14

also, just minor thing but [clojure.test :refer [deftest is use-fixtures] should work in both environments. don't need to reader conditional that one

bartuka14:02:10

I didn't know that. I thought I needed the reader conditional because of the macros.

bartuka14:02:20

cool! Thanks!

kishima14:02:04

Hello there, What do you guys use to set up a rest api? I have seen people use Compojure and Ring but would like to know if it is the best option

bartuka14:02:45

best options are hard to tell rsrsrs. I like reitit now o/// #reitit

kishima14:02:47

Okay, maybe best options is a little too much to ask, my bad hehe I'll give this one a look, thanks!

dharrigan15:02:48

I use reitit too

dharrigan15:02:19

For what it's worth, I've put up a little twitter bot that tweets a random clojure function of the day. You can find it here: https://twitter.com/rcfotd. It's new, doesn't do anything clever, but it's kinda cute 🙂 Enjoy!

👍 24
hindol15:02:46

That's quite useful actually. Sometimes I forget the less used fns.

borkdude15:02:02

I swear I didn't see that thing before I wrote the script 🙂

dharrigan16:02:08

It was a massive co-incidence!

dharrigan16:02:18

I saw your posts too and I was like "wow!"

borkdude16:02:08

I did see the idea here before: https://github.com/tomekw/cotd

Ty17:02:08

In the definition for comp there’s a block for composing two functions. I’m wondering why it handles 1, 2, 3, and n arguments differently. Couldn’t it just use apply for any length?

hiredman17:02:48

it is a performance optimization

Ty17:02:02

Ah okay.

Ty17:02:26

So apply is slow?

hiredman17:02:44

not slow, just slower

seancorfield17:02:09

@tylertracey09 If you look at the source of several core functions, you'll see a similar "unrolling" of the first few arities -- as a performance optimization.

Ty17:02:48

Good to know.

Ty17:02:06

Can anyone recommend me a clojure book for learning purposes? I do best when I have some exercises to do as well

seancorfield17:02:29

@tylertracey09 There's Clojure for the Brave and True (available for free online as well), Getting Clojure, Living Clojure -- those are the three that come to mind. Not sure which of those actually have exercises in them.

hindol17:02:26

For exercises, 1) Clojure Koans and Exercism for basic staff, 2) 4clojure for slightly harder puzzles.

hindol18:02:53

Might be just me, but as I know another language already (Java), the 1) Guides and 2) References section on http://Clojure.org were excellent resources for me. Books were too slow.

Behrokh Farzad20:02:32

has anyone had a problem with downloading tools.deps in IntelliJ IDEA settings? I keep getting the following error Cannot execute, please download tools.deps in Settings: Cannot execute, please download tools.deps in Settings

fbgava04:02:47

Hi Behrokh, not sure if you already solved the issue, but it's a problem with Cursive and it's fixed in EAP only. If you don't want to download that, you can fix like this: Go to Build Tools -> Clojure Deps: Use private repo: ID: Central / URL: https://repo1.maven.org/maven2/ Then refresh and download.

👍 8
lance.paine16:02:57

thanks @U7JCZJR0W I was suffering the same.

Behrokh Farzad19:02:27

thanks @U7JCZJR0W it’s working now.

Guillermo Ithier20:02:11

Hello Clojurians, I'm having a problems getting access to a zero-token through a POST so that I can use it in my buffer to perform an authentication request. Now the URL here's the link ; following along a video 'Microservices with CLojure'.  I'm lost on how to use this site to create a way to well, access to a zero-token through a POST so that I can use it in my buffer to perform an authentication request. Here is the relevant functions that calls the outdated api: (defn auth0-token []
  (let [ret
        (client/post ""
                     {:debug false
                      :content-type :json
                      :form-params {:client_id (System/getenv "AUTH0_CLIENT_ID")
                                    :client_secret (System/getenv "AUTH0_SECRET")
                                    :grant_type "client_credentials"}})]
                                  (json/parse-string (ret :body))))

Jimmy Miller20:02:00

People might be able to help more if you describe what is and isn't working. When you run your code what does it do that you didn't expect? What do you want it to do?

Guillermo Ithier20:02:43

Can someone help me with this? Thank you in advance 🙂

dpsutton21:02:36

I imaging (:headers ret) will have some info there

Ty22:02:06

Is emacs the go to for clojure?

bfabry22:02:14

if you already know emacs yes. if you're just learning clojure you probably don't want to start learning both emacs and clojure. in which case I personally recommend Cursive for IntelliJ. Some others like VSCode with Calva and Atom

seancorfield23:02:45

@tylertracey09 If the editor/IDE you are already using supports Clojure, I'd stick with that while you are learning Clojure. What are you using now?

Ty23:02:00

I use VSCode

Ty23:02:16

Really all I want is to be able to evaluate an expression in line

Ty23:02:03

I guess that’s what folks use the REPL for, but I saw a screencast where the guy was using emacs and he had a command that would evaluate the expression under the cursor or something

bfabry23:02:51

calva with VSCode can do that

Ty23:02:22

Oh neat I will check into it, thanks

Ty23:02:47

I think I’m using a different plugin atm. I will try Calva instead

Ty23:02:05

Out of curiosity if you use emacs were you using it before clojure? I remember reading something about how emacs was implemented with a LISP and so you can write extensions using LISP as well. Is that what got you folks into it or did you migrate because of clojure tooling at the time?

seancorfield00:02:15

I used Emacs decades ago, back in the 17.x/18.x days. I moved away from it round about 19.x I think. I started using C++ IDEs then Java IDEs. Eventually, after a bunch of languages, I ended up in Clojure and went back to Emacs because it was the "best" / "most popular" -- and was shocked that it hadn't changed much in... twenty years?

seancorfield00:02:57

I went back and forth between Emacs and other editors for a while, when learning Clojure, then settled on Emacs... until I saw Atom/ProtoREPL at Conj one year! I switched to Atom and never looked back. ProtoREPL is no longer maintained (and I think it's actually broken in recent Atom releases?) but I switched to Chlorine over a year ago and I love that combination (Atom/Chlorine, plus Cognitect's REBL data browser, with a simple Socket REPL -- no nREPL etc).

johnj00:02:22

curious, why not the socket prepl?

seancorfield00:02:04

@U4R5K5M0A I think Chlorine starts a prepl once it has a plain socket connection.

seancorfield00:02:53

The socket REPL is nice because you can a) telnet/netcat to it from your phone b) connect to it via unrepl/unravel c) connect from Chlorine ...

hiredman23:02:08

it is a time machine

bfabry23:02:28

I'm honestly not sure how much knowing clojure would help you writing Emacs Lisp. they're dramatically different

hiredman23:02:30

visiting an alternate future branched off from this world in the 70s

Ty23:02:27

I wrote a heartfelt cover letter to David Nolen and the other folks over at Vouch. Here’s hoping I get the chance to learn from some experienced clojure developers