Fork me on GitHub
#beginners
<
2019-08-30
>
walterl00:08:50

Is there a "canonical" predicate for falsey values nil, 0, false, [], {}, and '()?

noisesmith00:08:55

most of those are not falsey

noisesmith00:08:48

looks like you want #(or (not %) (zero? %) (and (coll? %) (empty? %)))

noisesmith00:08:05

there's nothing built in that combines those

andy.fingerhut00:08:49

If you want nil and false to be considered logical false, and every other value logical true, as Clojure if and cond do, for example, you could use boolean as a predicate.

Tilman00:08:07

I wanted to try our clojurescript for the first time because I love clojure. For the last 4 hours I tried to get a minimal working example of a cljs/reagent app to run. I used all kinds of tutorials in blog posts or on YouTube. None of them worked, most of them did not even get me to a working repl, aborting with a 300 line long stack trace after downloading stuff for 4 minutes. I have tried chestnut, figwheel both with and without leiningen. Could someone be so kind to recommend an up-to-date guide/tutorial to set up a minimal reagent app? After failing miserably for the last 4 hours that would be very much appreciated. 🙂

noisesmith00:08:52

I think the luminus template for clj/cljs is the most thoroughly documented and up to date right now

noisesmith00:08:02

but someone else might have fresher info

chepprey00:08:31

@tilman.schieber I had success with the free "learn reagent" tutorial, not sure if that's one of the ones you tried. https://www.learnreagent.com/

Tilman00:08:06

I have not tried these two, thanks for the tips. Has so much changed in the last years that all older tutorials are deprecated?

chepprey00:08:57

No idea, I have been interested in clojure for years but have only started my own projects in it in the past year. I did this tutorial in March. I was attracted to it because it required a very minimal amount of "stuff". It uses shadow-cljs with minimal deps, you can use any editor you like, and it focuses on the ideas and concepts with simple examples. Loved it.

seancorfield00:08:41

@tilman.schieber Here's what I just tried (and it worked for me):

> lein new luminus tilman +reagent
...
> cd tilman
> lein run
... starts HTTP server and nREPL server ...
then in another terminal in the same project folder
> lein figwheel
...
waits for you to visit http://127.0.0.1:3000 in your web browser
Prompt will show when Figwheel connects to your application
[Rebel readline] Type :repl/help for online help info
ClojureScript 1.10.520
app:cljs.user=> 
and you can connect an nREPL client (e.g., your editor) to 127.0.0.1:7000

💯 4
noisesmith00:08:21

@tilman.schieber about deprecation, yeah cljs has moved pretty fast

noisesmith00:08:42

@tilman.schieber it's also worth getting the Web Development in Clojure book by Sotnikov, it uses luminus iirc

seancorfield00:08:08

ClojureScript has evolved very, very fast and a long way over the last few years. We tried it at work back in 2014/2015 I think and it was... not a good experience... but I've recently come back to trying it again, and I'm following along with Dimitri's book in the new edition (which is in beta right now).

noisesmith00:08:10

Gain expertise in the popular Ring/Compojure stack using the Luminus framework. 
I remembered correctly

seancorfield00:08:12

Yeah, that one... it's good.

seancorfield00:08:42

The new edition has been updated to "current" cljs techniques but this whole area moves so fast 🙂

Tilman00:08:35

@seancorfield thank you! I'll try that now

seancorfield00:08:38

Chapter 4 introduces cljs and Reagent and chapter 5 introduces shadow-cljs (a change from Figwheel in the previous edition).

seancorfield00:08:13

Also, did you run through this -- the official ClojureScript Getting Started Guide? https://clojurescript.org/guides/quick-start

Tilman00:08:44

Yes. It was one of the things that didn't work for me

seancorfield00:08:58

(and I suppose I should ask: what platform are you on? Windows is kind of a second-class citizen in the Clojure world)

seancorfield01:08:40

Hmm, I would expect the official guide to "just work" on Linux 😐

Tilman01:08:08

But I think open jdk12 might have been a problem. After downgrading to an older Java it worked at least to the point of showing a repl

noisesmith01:08:37

oh yeah, 12 would be ouchy

seancorfield01:08:57

Ah, yes, JDK9+ definitely threw a bit of a wrench in things but JDK11 should mostly be OK these days with Clojure/Script.

noisesmith01:08:22

since 12 isn't ltr I don't think anyone has really tried to make clojure work with 12

Alex Miller (Clojure team)02:08:59

I’ve used both 12 and 13, not aware of any issues

seancorfield01:08:30

I'm still using JDK8 (Zulu OpenJDK with JavaFX bundle locally, AdoptJDK OpenJDK elsewhere)...

noisesmith01:08:17

the tl;dr about the newer vms is that part of the new java changes is being more strict about some kinds of shenanigans that allow access to other modules, and clojure (like most languages that try to do dynamic compilation and target jvm bytecode) used some of those shenanigans

Tilman01:08:57

Yes I just had a fresh install and installed the default jdk.

Tilman01:08:00

Thanks for the quick help. I should've asked you three hours ago. Luminus with leiningen (what @seancorfield recommended) seems to work just fine

4
seancorfield01:08:30

That's what we're here for! 🙂

seancorfield01:08:42

I expect to go back to Dmitri's book and renew my cljs learning in September. My wife is away for two weekends (Shanghai, China and Milwaukee, WI, judging cat shows) so I'll be all alone with nothing to do but read books and play in the REPL 🙂

Tilman01:08:23

Technologies that are developing so fast are so hard to find good information on without someone pointing you in the right direction. I'll definitely think about buying the book. I love pragmatic programmers

seancorfield01:08:26

Yeah, I have 19 of the PragProg books in my DropBox account 🙂

seancorfield01:08:28

Mostly Clojure books...

seancorfield01:08:45

Well, maybe not mostly but quite a few 🙂

Tilman01:08:40

Yeah I love the "seven X in seven weeks" books

Tilman01:08:22

So thanks again, with cljs running, I can happily go to bed now.

sova-soars-the-sora02:08:40

So I have 4 wrong answers now thanks to (take 4 (shuffle wa2)) but I also have the right answer on hand, and would like to throw it into this mix.

sova-soars-the-sora02:08:58

ah i got it, just did a concat and another (shuffle) 😃

Patrick P.05:08:43

ive liked the book "living clojure"

Darrell14:08:48

Hi all. New to Clojure and learning by jumping into the deep end on a freelance job. I'm trying to find some documentation on this syntax: {config as ::conf/config} where conf is required with (:require [myapp.config :as conf]).

noisesmith14:08:42

that's invalid, hash-maps don't accept odd numbers of keys, even in special syntaxes

noisesmith15:08:16

::conf/config is just a shorthand that expands to :myap.config/config

noisesmith15:08:53

:as is used to bind values in destructuring, but you can't bind to a keyword

Darrell15:08:09

I should have added more context: (defroutes routes (GET "/foo/bar" [:as {config ::conf/config}] ...

noisesmith15:08:35

OK, defroutes is weird, but at least that's valid for the clojure reader :D - note that :as is outside the map

Darrell15:08:58

It’s part of the Ring library I think.

noisesmith15:08:15

it's saying that ::conf/config of the incoming request is bound to the symbol config in the ... code

noisesmith15:08:25

it's part of compojure

noisesmith15:08:10

compojure routes run inside an implicit destructure, https://clojure.org/guides/destructuring - this guide might help

Santiago16:08:36

anyone using Netlify to deploy clojure? I’m not sure what build command I should use. I’m using lein

Chase16:08:10

I'm curious why my attempt at the simple robot name problem on exercism doesn't work right. https://pastebin.com/RKqTsujQ

Chase16:08:23

When I run each individual function in the repl they all work as I think they should but when I evaluate the let expression at the bottom (taken from one of the tests), it returns the same name for all 3 bindings.

markmarkmark16:08:49

notice how a-robot is used for every call in the let bindings. When (robot-name/reset-name a-robot) is called, it's not returning a new name. it's changing the value of a-robot in place.

markmarkmark16:08:05

and I guess specifically notice that a-robot is not being rebound in the let.

Chase16:08:32

So how exactly is the test's its-new-name supposed to return a new name with how they have that set up then?

Chase16:08:29

here is the full test:

(deftest reset-name
  (let [a-robot (robot-name/robot)
        its-original-name (robot-name/robot-name a-robot)
        its-new-name (do (robot-name/reset-name a-robot)
                         (robot-name/robot-name a-robot))]

    (testing "reset-name"
      (is (re-seq #"[A-Z]{2}\d{3}" its-new-name)
          "new name matches expected pattern")
      (is (not= its-original-name its-new-name)
          "new name is different from old name")
      (is (= its-new-name (robot-name/robot-name a-robot))
          "new name doesn't change until you reset it")
      (is (not= its-new-name (do (robot-name/reset-name a-robot)
                                 (robot-name/robot-name a-robot)))
          "new names are different each time"))))

markmarkmark16:08:44

it works because the functions are wrapped in a do

markmarkmark16:08:30

the first call in the do does the side-effectful change to a-robot and the second call gets the current name from a-robot

markmarkmark16:08:57

a do will evaluate multiple forms in order and will return the value of the last form.

Chase16:08:41

ok. so I think I need to make sure reset-name actually changes a-robot and doesn't just generate a new random robot name, huh?

markmarkmark16:08:10

yeah, a-robot can't just be a String name, it needs to be some kind of holder of a name that can change.

Chase16:08:21

that sounds like mutation! or can it just be changed locally like in a let binding. let me try and wrap my head around this and play around some more.

Chase16:08:28

thanks for leading me towards a solution!

markmarkmark16:08:45

no problem 🙂

Jazzer16:08:38

Hi all. What is the difference in spec between using (s/with-gen ...) and (s/spec ... :gen ...)?

Jazzer16:08:30

I'm seeing each used in different examples online, but they seem to be doing the same thing...

Alex Miller (Clojure team)17:08:08

kind of just accidental artifact of different api explorations

Alex Miller (Clojure team)17:08:25

s/coll-of has a :gen key too that's another overlap

Alex Miller (Clojure team)17:08:53

I suspect we will winnow some of those down in spec 2

Jazzer17:08:43

Ah ok. Thanks for explaining - I thought that might have been the case, but wanted to check I wasn't missing something.

Jazzer17:08:10

I saw spec2 while doing a bit of googling for this. Looks like it's coming on nicely!

Jazzer18:08:31

Is it possible to pass data into a spec generator? I'm trying to "compose" generators so that I can generate a sub-map according to data already generated at the top level

Jazzer18:08:14

The idea would be generate a tuple, then pass one of those tuples into another generator to generate a random sample (e.g. a map of that particular size)

Jazzer18:08:36

I've not explained that very well, so I'll try and work up a simplified example of what I'm trying to achieve...

seancorfield18:08:25

@j.m.frith is gen/fmap what you are looking for?

Jazzer19:08:37

As a simplified example, I am trying to create a map like

{:deck/cards {:trumpet {:value 1}
              :snake {:value 8}
              :exhaust {:value 0}}
 :game/card-1 :trumpet
 :game/card-2 :snake}
so I was intending to generate a random set of keywords which I can pass into my generator which can create a deck from the cards (by generating random :value), but then also use the same set of keywords to choose a random element for the two :card values

Jazzer19:08:12

That way I can generate a complete game map. But if a function only needs the :deck/cards as input, I'd like to use the generator to generate a random set of keywords and then stick in :values

Jazzer19:08:03

Actually just asking the question tells me, maybe in the first instance I should just generate a completely random deck, then extract the keywords that were generated in that to choose what should be in the :game/card-1 and :game/card-2 values

noisesmith19:08:32

the advantage of using fmap for this is it helps to preserve the reproducability of generation based on a seed

noisesmith19:08:44

(in case that isn't clear)

Jazzer19:08:00

Hi @seancorfield I think it's part of what I'm looking for, but really I'd want to use it to pass parameters into a second generator (i'm just not sure that part is possible/sensible)

noisesmith19:08:45

fmap takes a generator and a function, generators don't take args, but your function can generate another value and combine it with what the original generator made

Jazzer19:08:25

Thanks @noisesmith I think I'm getting close to understanding, I guess what I'd ideally like is rather than the function combining it with what the original generator made, it could modify the original generator to produce only a subset of what it otherwise could do.

Jazzer19:08:57

I'm wondering if I should write a separate function which would return a generator with the properties I want

noisesmith19:08:24

you can literally filter with gen/such-that

Jazzer19:08:54

Yeah, but such-that needs it to be relatively likely that things will satisfy the criteria

Jazzer19:08:48

It's a bit like the general spec calls for a vector with length between 1 and 10, but if data elsewhere in my spec has a value of 9, then the list has to be of length 9

noisesmith19:08:50

right, so you can use s/with-gen and gen/fmap to make a a generator that that is more likely to create the correct values

Jazzer19:08:32

I was originally thinking that I should generate the 9, then I can "pass that into a generator" to generate a vector with 9 entries.

noisesmith19:08:35

hard-coding length 9 is a perfect usage for fmap

noisesmith19:08:00

sure, higher order function can take 9 and return an fmapping function, if that's the semantics you want

Jazzer19:08:26

Would I be better off generating a random length vector, and then putting whatever length it is into the other part of the spec?

noisesmith19:08:58

specs don't really do that self-referential thing, but custom generators can

Jazzer19:08:52

I think that's what I'm struggling to get my head around. The spec at a higher level can enfore the relationship, but ideally I'd like to be able to have generators that will work at whatever level they are

noisesmith19:08:13

I'd prefer providing that info explicitly, over duplicating info (AKA making the spec brittle by having two values that can go out of sync and produce something incoherent)

noisesmith19:08:44

IMHO a spec should already be de normalized (fixed typo)

Jazzer19:08:00

OK. Thank you. I will go and think about how I can achieve this. It's been really helpful!

johnjelinek19:08:22

Why can't (source get my function definition?

user=> (defn sq [x] (* x x))
#'user/sq
user=> (sq 2)
4
user=> (clojure.repl/source sq)
Source not found
nil

noisesmith19:08:45

source doesn't get data from the function, it uses file / line metadata

noisesmith19:08:51

you can't find a file for stdin

johnjelinek19:08:24

oic -- so, there's no function that can recall the definition of something that I make in the REPL?

noisesmith19:08:44

it's never stored, right

noisesmith19:08:57

the repl/source function uses metadata to reread the souruce

noisesmith19:08:41

compare

(ins)user=> (defn foo "this is foo" [])
#'user/foo
(ins)user=> (pprint (meta #'foo))
{:arglists ([]),
 :doc "this is foo",
 :line 1,
 :column 1,
 :file "NO_SOURCE_PATH",
 :name foo,
 :ns #object[clojure.lang.Namespace 0x6a969fb8 "user"]}
nil
(ins)user=> (pprint (meta #'conj))
{:added "1.0",
 :ns #object[clojure.lang.Namespace 0x57b9e423 "clojure.core"],
 :name conj,
 :file "clojure/core.clj",
 :static true,
 :column 1,
 :line 75,
 :arglists ([coll x] [coll x & xs]),
 :doc
 "conj[oin]. Returns a new collection with the xs\n    'added'. (conj nil item) returns (item).  The 'addition' may\n    happen at different 'places' depending on the concrete type."}
nil

noisesmith19:08:12

the metadata of conj contains :file and :line and :column such that clojure.repl/source can find the source and print it

johnjelinek19:08:43

I've not used (meta before:

user=> (clojure.pprint/pprint (meta #'sq))
{:arglists ([x]),
 :line 1,
 :column 1,
 :file "NO_SOURCE_PATH",
 :name sq,
 :ns #object[clojure.lang.Namespace 0x3d7b1f1c "user"]}
nil

johnjelinek19:08:21

so ... if I want to read the source of a function I made in the repl as data for another function .. what's the function I should use for that?

johnjelinek19:08:30

or should I quote it and use it that way?

noisesmith19:08:29

sadly clojure source code isn't first class like that (though there are libs with their own version of fn / defn that provide this)

mfikes19:08:16

FWIW, Lumo, Planck and Replete all do this

johnjelinek19:08:27

Oh? Could you link to one or more of those libs?

mfikes19:08:44

Oh, sorry, those aren't libs, but REPLs

johnjelinek19:08:20

You're right! This is functionality I wanted in my rebel-readline

johnjelinek19:08:19

@mfikes: is this capability unique to cljs REPLs?

mfikes19:08:38

No, it is just a revision to the way those REPLs behave. If you are curious, it just captures the source for REPL-entered forms. https://github.com/planck-repl/planck/commit/f16e6187f09a0201b3b2cefbfd8e0c1d75fa053d

johnjelinek19:08:10

I guess (::repl-entered-source var) is the magic

mfikes19:08:30

Hah, yeah. Only a little magic. 🙂

seancorfield20:08:21

@johnjelinek My advice: develop a workflow where you never type into the REPL -- always put the code in a file, connect your editor to the REPL, and eval forms from the file into the REPL.

👍 4
💯 4
seancorfield20:08:29

Stu Halloway and many others advocate for that -- never type into the REPL. You can put (comment ,,,) forms in your (source/test) files for code you are just experimenting with and do not want run as part of your project, then you can eval the forms from inside that comment block as and when you need.

johnjelinek20:08:49

I was really hoping to do more with rebel-readline instead of using my editor, but that's ok

seancorfield20:08:59

The REPL is fine for really basic exploration but it's all throwaway work (well, the REPL history is actually saved to a local file -- and you can scroll back in the REPL in all except the very basic clojure.main REPL). Editor integration is definitely the way to go.

seancorfield20:08:02

It's why I like REBL so much: it does have an input REPL (well, it has two) and it shows all expression history -- even from your editor -- and lets you scroll back and forth and drill into values and display them in different ways etc...

seancorfield20:08:30

...but I drive it almost entirely from my editor (in terms of evaluation).

johnjelinek20:08:09

@seancorfield: is there any good written resources you recommend to incorporate REBL into my workflow?

seancorfield20:08:59

Written? No, not really. Much will depend on what editor you use and what "build" tool you use.

seancorfield20:08:21

I've done a few screen casts of my workflow with Atom/Chlorine/REBL up on YouTube

seancorfield20:08:48

(but it all assumes CLI/`deps.edn`, my dot-clojure repo, and my atom-chlorine-setup repo 🙂 )

seancorfield21:08:57

In case anyone is curious at this point, if you have the rebl-8 and nrepl aliases in deps.edn based on my dot-clojure repo, you can get nREPL + REBL up and running with Rick's middleware like this

clj -R:rebl-8:nrepl -Sdeps '{:deps {rickmoynihan/nrebl.middleware {:mvn/version "RELEASE"}}}' -e "((requiring-resolve 'cognitect.rebl/ui))" -m nrepl.cmdline --middleware '[nrebl.middleware/wrap-nrebl]' -i

seancorfield21:08:13

(assuming you're using Clojure 1.10 or later because... why wouldn't you?)

seancorfield21:08:30

That command starts the REBL UI, starts an nREPL server, and starts an interactive REPL, all connected. You can then connect your editor/whatever to nREPL on the port displayed as it starts up, e.g.,

nREPL server started on port 58945 on host localhost - 
nREPL 0.7.0-alpha1
Clojure 1.10.1
OpenJDK 64-Bit Server VM 1.8.0_222-b10
Interrupt: Control+C
Exit:      Control+D or (exit) or (quit)
user=> 

seancorfield21:08:07

You may want the CIDER middleware in there too (if you're using Emacs) -- see Rick's project README

johnjelinek22:08:11

cool, thanks!

johnjelinek22:08:57

next topic: by convention, should any function that involves side-effects be appended with a ! to the function name?

johnjelinek22:08:24

(e.g: write to file / save to database / swap atom)

seancorfield22:08:26

It depends 🙂

seancorfield22:08:16

The idea behind ! is to identity functions that you should not call as part of an STM transaction (as I recall) but that would mean that ! would filter all the way to the top of your call stack on nearly everything...

seancorfield22:08:44

...so, in reality, it's normally used just for low-level functions that are known to modify "the environment".

seancorfield22:08:41

next.jdbc/execute! and next.jdbc.sql/update! for example, but I don't typically add ! to functions that call those (unless they are thin wrappers that a lot of other code would call instead).

johnjelinek22:08:37

cool, thanks!

johnjelinek22:08:16

really? no one uses refs? not even for transactions where you're going to be producing side-effects in different places?

andy.fingerhut22:08:13

I wouldn't say that no one uses Clojure refs. They are seldom used, because atom tends to be enough for most purposes.

andy.fingerhut22:08:32

e.g. if you have a big map of keys and values, perhaps nested, storing a bunch of configuration data, a single atom is enough to make atomic changes to arbitrary parts of that, all at once.

zane22:08:57

This 👆:skin-tone-2: was one of the bigger surprises to me when I first got started with Clojure.

zane22:08:05

I thought refs were going to be a much bigger deal.

andy.fingerhut22:08:24

I think a lot of people do, when they first see them.

zane22:08:05

Yeah. They were hyped to me a lot when I was reading up on Clojure.

seancorfield22:08:54

A lot of people see STM as "unique to Clojure" and so they expect it to be used a lot. But mostly Clojure code avoids side-effects so much that you don't need STM much -- beyond a few atom and occasional agent calls.

zane22:08:36

Yes. I've been using Clojure in my day job since around 2013 and I don't think I've ever reached for refs / dosync.

johnjelinek22:08:32

so ... let's say I'm primarily going to be writing cloud native stateless processes (maybe they run in containers or lambdas), so I'll defer concurrency concerns to the infrastructure of the cloud -- do I lose a lot of the power of clojure by not leveraging its concurrency and parallelism capabilities?

johnjelinek22:08:50

like ... would you say clojure is the wrong tool for the job in that context?

zane22:08:51

I would definitely not say that. You'll still get a lot of value out of the rest of Clojure.

andy.fingerhut22:08:17

I have heard other Clojure developers say that if they had to choose between a language that had the syntax of some Java/Python/Ruby/etc. language, but Clojure's immutable data structures, vs. Clojure syntax but mutable data structures, they would go for the immutable data structures every time.

andy.fingerhut22:08:29

Even for single-threaded programs.

andy.fingerhut22:08:14

large mutable data structures, even in a single-threaded program, are a source of incidental complexity, bugs, difficulty-in-people-reasoning-about-code-behavior, etc.

zane22:08:59

That's definitely how I feel.

zane22:08:36

In many ways immutable data structures are the best thing about Clojure to me. It's why I'm a bit OCaml-curious.

tjb22:08:40

hey everyone! im trying to validate clojure as a viable option for an API server but am curious about a few things 1: Is there a defacto standard for http/restful endpoints? I have come across - Ring - Luminus 2: Is there a lib for JWT? I have come across - Buddy But it hasn’t been updated in two years? can anyone provide me any guidance here on good resources i could read up about auth / endpoint creation

johnjelinek22:08:28

@tjb: I think you may want to take a look at pedestal and then I think buddy should be fine

👍 4
tjb22:08:43

is that something to be concerned about when a lib seems abandoned?

zane22:08:52

My sense is that Ring is the de-facto standard for HTTP in Clojure.

tjb22:08:09

gotcha. i wonder with JWT maybe the answer is to just use a java lib for it

tjb22:08:22

im just worried about using dependencies that have no activity

zane22:08:18

Activity is not necessarily a sign of deprecation in Clojure-land. Since Clojure itself is extremely stable a lot of libraries simply get to a state where they're more or less complete and modifications aren't necessary.

👍 4
skuttleman22:08:23

I've used buddy in production at my last job and my current one. Sometimes no updates means no problems.

👍 4
zane22:08:25

This trips a lot of new users up.

👍 4
tjb22:08:42

some background: i have a node server for my side project but really want to expand my FP knowledge. my day job is java stuff so staying on the jvm seems like the right move. in comes clojure. since im primarily a web dev im want to validate using clojure for api server-related things before mentioning it @ work as a viable option for things

4
tjb22:08:27

cool cool, thanks everyone!

tjb22:08:30

what a friendly community 🙂

❤️ 8
David Pham22:08:07

Ring is good for learning

David Pham22:08:32

I think most other librairies start with ring as basis

tjb22:08:21

more bare-bone the better. or anything similar to what express (node) is would be awesome too

tjb22:08:28

i assume that would be Ring?

zane22:08:30

Re: "de-facto standard" — You'd probably be interested in this quote from Rich on web frameworks: >>> JOY CLARK: … So is there a benefit to having a standard stack, where you can say "This is what you should use?" RICH HICKEY: Well, some parts of that question are social, which I can't really speak to... I think certainly when somebody figures out how to do web development, they should encode it in a framework. But I'm not sure that that's a solved problem, and I think until it is a solved problem, opinions are very much opinions, and therefore you're at risk adopting a set of opinions that may not be an answer. — http://www.dustingetz.com/:rich-hickey-web-frameworks/

👍 4
zane22:08:14

Yes, that would definitely be Ring. It's a pretty minimal HTTP request/response abstraction.

tjb22:08:31

ok awesome!

David Pham22:08:33

Yeah, I still like all the middleware xD

zane22:08:21

I personally found that implementing some Ring middleware by hand helped me understand each of the Ring-based libraries better, and how to choose between them.

David Pham22:08:27

I love lein ring for developing the API

David Pham22:08:53

Yeah, it makes a lot of sense to develop some middleware

tjb22:08:02

awesome thanks everyone i really appreciate it. im going to dive into it tomorrow and experiment. i will post my learnings tomorrow night!

zane22:08:36

I believe that Pedestal is analogous to Ring, but my impression has been that it gets less use overall (even though it's produced by Cognitect).

zane22:08:38

Sure thing!

seancorfield22:08:46

@tjb We have a production API Server built with Ring, Compojure (for routes), and some standard Ring middleware for handling JSON in and JSON out. Plus seancorfield/next.jdbc for handling JDBC communication with databases (and a bunch of other libs for external stuff).

👍 4
tjb22:08:17

do you recommend any type of project structure? (directory structure)

seancorfield22:08:54

@tjb If you have the Clojure CLI installed, you can create a basic Compojure project like this

clj -Sdeps '{:deps {seancorfield/clj-new {:mvn/version "0.7.8"}}}' -m clj-new.create compojure tjb/api-example
and that will give you a typical structure for a Clojure project.

seancorfield22:08:15

If you have Leiningen installed, lein new compojure tjb/api-example should do the same

seancorfield22:08:30

As you write more code, you'll have more namespaces under src and (hopefully) the matching namespaces under test with unit tests in.

seancorfield23:08:34

The compojure template assumes you'll use lein to do things with the project, but it would be easy enough to add deps.edn with the equivalent dependencies

seancorfield23:08:16

(and unfortunately it assumes you'll use lein ring ... to start the project which I really don't like but you should be able to find tutorials to get up and running quickly that way)

seancorfield23:08:25

If you want to see a more fleshed out web app example, just to get you started https://github.com/seancorfield/usermanager-example (not an API but it works with clj / deps.edn from the get-go and shows how to do DB access etc)

tjb23:08:19

Awesome thank you so much @seancorfield !!!

johnjelinek22:08:01

what is reduce1 and why can't I (source it?

user=> (clojure.repl/source +)
(defn +
  "Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'"
  {:inline (nary-inline 'add 'unchecked_add)
   :inline-arities >1?
   :added "1.2"}
  ([] 0)
  ([x] (cast Number x))
  ([x y] (. clojure.lang.Numbers (add x y)))
  ([x y & more]
     (reduce1 + (+ x y) more)))
nil

seancorfield22:08:18

We use JWT and I think we just wrapped the Apache lib for it... let me check...

Alex Miller (Clojure team)22:08:29

you should just read reduce1 as reduce

johnjelinek22:08:53

does (reduce1 basically do a (loop [] (recur?

seancorfield22:08:12

(:import (org.apache.oltu.jose.jws
            JWS$Builder
            signature.impl.SymmetricKeyImpl
            signature.impl.SignatureMethodsHMAC256Impl)))
Yeah, org.apache.oltu.jose/org.apache.oltu.jose.jws

👍 4
seancorfield22:08:44

(`clojure.core` == "Here Be Dragons!" 🙂 )

Alex Miller (Clojure team)22:08:25

bootstrapping is weird sometimes :)

andy.fingerhut22:08:29

IIRC, reduce1 has the same API as reduce, but reduce1 might not have all of the performance optimization goodness that reduce has.

johnjelinek23:08:58

I'm a little surprised I don't have to (first and (next here with the more:

user=> (defn sq
  #_=>   ([x] x)
  #_=>   ([x y] (reduce * (repeat y x)))
  #_=>   ([x y & more] (reduce sq (sq x y) more)))

seancorfield23:08:44

@johnjelinek Because [x y & more] destructures the arguments for you... so (sq 1 2 3 4) becomes x : 1, y : 2, more : (3 4)

Danny23:08:32

is there a way to import a function from another file with a different name (i'm trying to overwrite fdef with >fdef from ghostwheel)

johnjelinek23:08:27

(:require [ghostwheel :refer [fdef :as >fdef]]) is this a thing?

johnjelinek23:08:05

@dfehrenbach04: this works for me: (require '[clojure.repl :refer [doc] :rename {doc docs}])

seancorfield23:08:05

There's :rename which takes a hash map

johnjelinek23:08:49

so, you could prolly (:require [ghoswheel :refer [fdef] :rename {fdef >fdef}])

Danny23:08:26

Yeah that worked. Thanks a million! Ghostweel's being... annoying when it comes to playing nicely with Calva in vscode. That's the next problem to fix on my list. (Tons of "problems" including being unable to "resolve symbols" when using the >defn from ghostwheel 😞 )

seancorfield23:08:06

@johnjelinek In what way? (I guess if you have a math background it's straightforward)

johnjelinek23:08:31

ya, that's prolly my problem -- I don't have a math background and most of my experience has been trying stuff out and seeing what happens -- which is what drew me to clojure, because I could get a similar fast feedback loop like I'd get in SQL compared to other languages where my feedback comes back slower (unless I partition a segment in unit tests)

johnjelinek23:08:37

but as you can see -- what I wrote above to for (sq compared to (exp-int ... and you can prolly see how they're at different levels

seancorfield23:08:00

It's a fast way to calculate exponents: you square the value and half the exponent and adjust by tracking the "remainder" multiplication at each time round the loop.

seancorfield23:08:36

If you compared your sq and that expt-int side-by-side written in C or Java they'd still look pretty different

andy.fingerhut23:08:11

I think a fair number of comp sci students find that novel when they first see it. I know I did not come across it until grad school, and wondered how I had never thought of it before.

andy.fingerhut23:08:16

Even more cool when I first saw it: using multiplication of 2x2 matrices of the appropriate contents in order to calculate the N-th Fibonacci number in log N matrix multiplications.

andy.fingerhut23:08:39

or maybe they are 3x3 -- long time since I looked at it: https://www.geeksforgeeks.org/matrix-exponentiation/