Fork me on GitHub
#beginners
<
2021-01-19
>
Piotr Brzeziński11:01:09

I’m going through 4clojure exercises and was asked to create palindrome checker. What I’ve done is

(defn palindrome? [v]
  (loop [original v reversed (reverse v) result false]
    (if (empty? original)
      result
      (if (= (first original) (first reversed))
        (recur (rest original) (rest reversed) true)
        (recur (rest original) (rest reversed) false)))))
then I went to see other’s solutions and I ran into
(defn palindrome? [v] (= (reverse v) (reverse (reverse v))))
and I felt stupid 😄

😀 9
dpsutton15:01:27

There’s a bug in your code. It looks like you will report if the last and last item are the same

👍 3
Piotr Brzeziński15:01:37

Ohh, great catch, thank you.

jaihindhreddy11:01:50

They could've substituted (reverse (reverse v)) with (seq v).

roelof11:01:05

know what you feel. IM a little but further on 4 clojure and still feel stupid @peb.brzezinski

Piotr Brzeziński11:01:17

I mean I’ve been a developer for couple of years and I still get that feeling every day 😄

roelof14:01:25

oke, so not wierd that I feel like a super beginner a lof when solving learning challenges

Piotr Brzeziński11:01:47

How does that work @jaihindhreddy? (= (seq [1 2 3]) (reverse [1 2 3])) why are they equal? :thinking_face:

jaihindhreddy11:01:20

seq returns a sequence from its argument. reverse returns a sequence of the elements in its argument in the reverse order. So, if we do (defn palindrome? [s] (= s (reverse s))), it won't do the right thing for strings, because the string "aba" and the sequence of characters '(\a \b \a) don't compare as equal. So, either seq or calling reverse twice on the input "converts it" to a seq, in a manner of speaking. See, https://clojure.org/reference/sequences and https://clojure.org/guides/equality in the docs for more details.. Turns out I made a mistake. seq returns nil when the argument doesn't have anything in it. So, my impl fails for the empty string, because (reverse "") returns the empty list, which is not the same as nil. So, this would be a correct implementation:

(defn palindrome? [s]
  (= (seq s)
     (seq (reverse s))))
That being said, if we wanted this function to work only with strings, this would be a more efficient way to do it:
(defn palindrome? [s]
  (= s (clojure.string/reverse s)))
PS: Here's a weird way I found to implement the same:
(def palindrome?
  (comp zero? dec count set (juxt seq (comp seq reverse))))

Piotr Brzeziński11:01:31

Nice, thanks a lot for the in detail explanation!

Piotr Brzeziński11:01:46

Point free FTW 🙂

noisesmith15:01:04

4clojure has some amazing and very stupid examples of point free programming

noisesmith15:01:30

(it's a fun exercise to see why they work, but they are often terrible as code per se)

💯 3
noisesmith15:01:48

anyway why not (comp zero? dec count set (juxt seq reverse) seq) (I almost put identity in there, but seq has a nice short name)

💯 3
jaihindhreddy19:01:18

@U051SS2EU that doesn't work on the empty string, because (reverse "") evals to (), which is not the same as nil.

noisesmith19:01:47

it calls seq first so both are nil

jaihindhreddy19:01:10

oops! misread it 😅

noisesmith19:01:29

(ins)user=> ((comp zero? dec count set (juxt seq reverse) seq) "aba")
true
(cmd)user=> ((comp zero? dec count set (juxt seq reverse) seq) "abc")
false

noisesmith19:01:07

@jaihindhreddy actually no you are right, I had it wrong

(cmd)user=> ((comp zero? dec count set (juxt seq reverse) seq) "")
false
(cmd)user=> ((comp set (juxt seq reverse) seq) "")
#{nil ()}
because (reverse nil) evaluates to () :/

roelof11:01:35

@peb.brzezinski what does reverse [1 2 3] returns ?

Piotr Brzeziński11:01:54

I know it returns (3 2 1)

Piotr Brzeziński11:01:04

But then I don’t get why (1 2 3) is equal to (3 2 1)

Piotr Brzeziński11:01:06

Seems like I need to read a bit about seqs. Anyway, thanks for making me dig deeper 🙂

jkrasnay11:01:36

@peb.brzezinski It’s not, because (1 2 3) is not a palindrome

👍 3
roelof13:01:17

I have to repeat every character in a seq 2 times. Is this a good solution or can it be improved

(defn my-repeat2[s]
  (flatten(map (fn[item](repeat 2 item)) s)))

Daniel Stephens13:01:47

I'd prefer mapcat to map+flatten, I also don't often use flatten at all since if your items happened to be vectors as well it would give you something unexpected (try your fn with [[1 2] [3 4]]). Also for the sake of two items, I wouldn't bother with a repeat, but that's personal preference.

(defn my-repeat2[s]
  (mapcat (fn [i] [i i]) s))

Mno13:01:52

What’s good or not depends on the tradeoffs you’re willing to make. I would strongly consider adding spaces to make it match more common spacing patterns:

(defn my-repeat2 [s]
  (flatten (map (fn [item] (repeat 2 item)) s)
For my favorite version of the implementation I would use interleave:
(defn my-repeat2 [s]
 (interleave s s))

roelof13:01:17

@UGFL22X0Q thanks . that is a lot o f shorter code

Piotr Brzeziński13:01:22

This is how I did the same thing

(seq (reduce #(conj %1 %2 %2) [] x)))

Mno13:01:53

the core library is full of gems for very specific use cases, but I’d argue it’s better to have a good grasp of map mapcat reduce since they’re much more versatile.

👍 3
Daniel Stephens13:01:29

One downside to using reduce here is that you loose any laziness, so (take 5 (my-repeat2 (range))) will never finish for example. interleave and mapcat don't have that limitation, but whether or not that's an issue is down to context!

👍 3
roelof13:01:13

as always it get down to context 🙂

noisesmith15:01:50

flatten is very brittle (breaks on common input types) and expensive (does a non-tail-recursive tree walk on input) - it's always better to use concat (or mapcat, or for) where possible

noisesmith15:01:59

user=> (for [el [1 2 3] e [el el]] e)
(1 1 2 2 3 3)

noisesmith15:01:56

(for is not as nice as other options here, but often fits nicely into other contexts - eg. where you are already using for and can do this in an extra binding clause)

lread13:01:39

@roelof and @peb.brzezinski when I compare my code that of more advanced Clojure developers, I too am often humbled. I’ve still much to learn, and I since I find joy in learning, especially amongst such friendly guides, I find that exciting.

3
roelof13:01:29

@lee I agree totally with you. The community is very helpfull here

Piotr Brzeziński13:01:59

Sure thing, this is the best way. I always enjoy a good feedback/peaking at a better solution. This is one of the reasons I try to work through 4clojure and one of the reasons I’ve been going back to exercism so often 🙂

roelof13:01:50

that are the two thing I do now. 4clojure for the practising and exercism for practising and the feedback of hopefully experienced developers

afry13:01:22

Hello all, does anybody have a quick reference they can point me to for setting up a networked repl connection? I'd like to remotely repl into a production app using the CLJ CLI. Thank you!

zackteo14:01:32

How do I make sure cloverage covers spec/keys. For some reason my test aren't covering most of them (under the forms %)

borkdude14:01:03

@andyfry01 socket REPL, pREPL, nREPL?

alexmiller14:01:24

there is no "socket REPL", there is a stream-based clojure.main REPL. all of these repls operate remotely over sockets.

andy.fingerhut17:01:49

I am not sure, but I think the phrase "socket REPL" is in fairly common usage today, isn't it? If you are trying to change that, you might want to start by calling it something else here: https://clojure.org/reference/repl_and_main#_launching_a_socket_server

andy.fingerhut17:01:21

I am not claiming this is "official" terminology, just commonly used terminology, e.g. it is all over the README and deps.edn file in this repo: https://github.com/seancorfield/dot-clojure

alexmiller17:01:28

well if you look carefully, you'll see that page says "socket server" (definitely a thing!) and "socket-based REPL"

alexmiller17:01:48

and "socket server with a repl listener"

alexmiller17:01:40

and then uses the shorter term near the end as shorthand (which I'm happy to un-shorthand if that makes it clearer)

alexmiller17:01:21

the big point really being that Clojure does not provide a socket repl, it provides a generic socket server, which can run any function, one example of which is a stream-based repl. prepl is another such example, a streaming data repl.

andy.fingerhut17:01:03

That all makes sense. I think developers would like a short name to call the thing they use when they use a generic socket server, which runs a stream-based repl. So far it seems like "socket REPL" is that short term used most often for that thing, as much as that name might be imprecise.

roelof15:01:35

What do you experts think of my solution of the re-implmentation of range

roelof15:01:39

(defn my-range [low high]
  (if (> low  high)
    ()
    (cons low (my-range (inc low) high))))

bronsa15:01:19

check (my-range 1 1) vs (range 1 1)

roelof15:01:48

solved

(defn my-range [low high]
  (if (>= low  high)
    ()
    (cons low (my-range (inc low) high))))

Daniel Stephens15:01:39

have you had a look at some big ranges, (take 1 (my-range 1 10000)), lazy-seq might be useful!

roelof15:01:32

oke, i have to think how to use that one

roelof15:01:21

is this well

(defn my-range [low high]
  (lazy-seq
  (if (>= low  high)
    ()
    (cons low (my-range (inc low) high)))))

roelof16:01:25

still wonders if this is good written clojure ?

Christian19:01:28

Instead of if you could work with when, some people might not agree though. I prefer it because it's short

roelof19:01:57

oke, I like this more. Found it on the net

(defn my-range2 [low high]
  (take-while
    (partial > high)
    (iterate inc low)))

Piotr Brzeziński16:01:35

One last question for today (I promise :P). I tried to implement dedupe function

(defn my-dedupe [val]
  (reduce #(if (= (last %1) %2) 
             %1
             (conj %1 %2)
             ) val))
, but when trying to execute it like (my-dedupe "aabbcc") I get an error Don't know how to create ISeq from: java.lang.Character and I’m not really sure what’s wrong here.

Thomas16:01:52

your anonymous function gets the first two items of val the first time it’s called

👌 3
noisesmith16:01:49

your paren placement is super weird

noisesmith16:01:05

try using an init for the collection rather than the implicit one (first item in (seq val) )

Piotr Brzeziński16:01:36

It was easier for me to read that way, but I get your point.

noisesmith16:01:51

instead of (reduce f "string") , (reduce f [] "string")

noisesmith16:01:43

@peb.brzezinski it will be easiest in the long term if we all agree on what the layout should look like - lisp conventions don't put hanging ) on its own line, especially not as a leading character before other forms

noisesmith16:01:19

it's kind of like driving on the same side of the road - it's easier if we all stick to the agreement

Piotr Brzeziński16:01:03

Sure, I have nothing against conventions. Thanks for pointing that out.

Piotr Brzeziński16:01:08

(fn [val]
  (reduce #(if (= (last %1) %2) 
             %1
             (conj %1 %2)) [] val))
this worked indeed. Thanks once again 🙂

noisesmith16:01:35

also, not everyone agrees here, but I prefer to align args horizontally if they are siblings - I'd write that out as

(fn [val]
  (reduce (fn [acc e]
            (if (= (last acc) e)
              acc
              (conj acc e)))
          []
          val))

Piotr Brzeziński16:01:05

Ah ok, so each arg to reduce on the same lvl.

noisesmith16:01:24

that makes it clearer to me - not everyone sticks to that of course

noisesmith16:01:51

also, if a #() is complex enough for line breaks, I prefer to be able to name args

noisesmith16:01:13

but I tend to waste more vertical space than others prefer

Piotr Brzeziński16:01:04

I prefer being explicit too in that case, I’m just trying to get used to the shorthand version of anonymous fns.

noisesmith16:01:27

(partial reduce
         #(if (= (last %1) %2)
             %1
             (conj %1 %2))
         [])

noisesmith16:01:17

(that's not how I'd ever write anything)

dgb2316:01:18

I think roughly 3 forms is a sensible limit for shorthand lambdas

noisesmith16:01:40

I'd never specified a number myself, but that sounds about right

noisesmith16:01:07

also I'd always replace #(do ...) with an fn that has implicit do

noisesmith16:01:25

and I never use #(let [... ...] ...)

dgb2316:01:45

Both of those give me anxiety. Good suggestion.

noisesmith16:01:15

this might just provoke a panic attack in that case

user=> (#(%2%1%):a{})
:a

dgb2316:01:55

why isn’t it nil?

noisesmith16:01:47

#(%2%1%) -> #(%2 %1 %) -> #(or (get %2 %1) %1)

noisesmith16:01:36

it can only get that ugly because #() uses reading rules for % that don't need whitespace

dgb2316:01:17

ohhh I didn’t see the last %

dgb2316:01:34

Wait isn’t it (get {} :a :a)

noisesmith16:01:24

right, I also expanded the get logic (not quite accurately)

dgb2316:01:34

Ahh right!

dgb2316:01:39

illustrative

dgb2316:01:17

the third parameter of get is quite handy I think

noisesmith16:01:20

yeah, it's actually a if-not-found not a logical or, but that's messier to illustrate

👍 6
noisesmith16:01:46

thanks to the third parameter of get, and the leniency, you can replace any usage of get with (get get get get)

noisesmith16:01:32

user=> (= get (get (get get get get) get get))
true

noisesmith16:01:30

it's the clojure code version of that famous "chicken" talk

dgb2316:01:43

Doesn’t ring a bell

dgb2316:01:22

Thank you so much, I’m in tears, had to watch it twice. What a genius!

noisesmith16:01:09

semantic satiation is a hell of a drug (and it's nice to avoid it in our code if we want clarity, clojure's good at that actually)

Ilan Uzan17:01:22

hi:) New here - quick question on IDEs. I've been used to working with VS code for some time now, but I understood there are much stronger IDEs for Clojure. Anyone had any experience with the VS Code extensions for Clojure and can recommend?

roelof17:01:39

I use Calva for some weeks and very very happy with it

🙌 3
dgb2317:01:48

VS Code with Calva gives you all the essential features

dgb2317:01:25

The intellij + cursive option is very good too. I don’t write a ton of Java, but my experience is that if you do, you want a full-blown IDE.

dgb2317:01:48

Emacs is often praised. But the two times I tried I ended up in configuration hell and decision fatigue.

noisesmith18:01:31

yeah, the emacs and vim integrations are quite featureful, but I wouldn't start learning emacs or vim just to use them with clojure

Joe18:01:30

Calva is great out-of-the-box and you get auto-formatting and linting right along with it. I'd recommend that for a Clojure beginner since it does a lot for you without having to understand all the detail yet.

Joe18:01:26

Oh, also Paredit :)

seancorfield18:01:05

I'll +1 VS Code for Clojure development (although I use Clover, rather than Calva -- Clover requires a bit more knowledge of stuff, since it uses a Socket REPL instead of the more common nREPL that Leiningen starts up).

pez18:01:22

@ilan800 welcome! I'm the creator of Calva. It's made with people for whom VS Code is home turf. Please see Tao of Calva and see if it seems to chime with you. https://github.com/BetterThanTomorrow/calva/wiki#the-tao-of-calva

🎉 9
Ilan Uzan18:01:22

Will take a look. Thank you!

Ilan Uzan18:01:00

Thank you all for the answers!:) I'll definitely start using Calva

pez18:01:42

Nice! Please feel welcome to #calva for assistance.

caumond18:01:17

Im not really trying to change your mind, but if you want to start with a predefined emacs setup ready for clojure, I would use #practicalli spacemacs.d configuration and all instructions proposed there. It is the closest from the out of the box I know. Its emacs, out of the box and still open

Hagenek18:01:51

Good evening, morning, night, day (depending where you live in the world!) Say I wanted add to an array this is found on a key in a map inside of a vector atom, how would I do that? Simple example: (def todos atom [{:id "1337 :desc "todo-list" :todos []} ]) How would I add todos this atom?

noisesmith18:01:57

@georghagen (swap! todos conj ...)

noisesmith18:01:43

@georghagen an atom is a container that manages the changes to its contents, the basic things needed are swap! and deref

noisesmith18:01:46

it looks like your atom has a weird structure, which means you would need to use a more complex call to swap! or use a simpler structure

Hagenek18:01:56

Any suggestions for a simpler structure?

noisesmith19:01:01

for example

(ins)user=>  (def todos (atom []))
#'user/todos
(ins)user=> (swap! todos conj {:what "homework" :when 0})
[{:what "homework", :when 0}]

noisesmith19:01:17

currently your "todos" seems to contain metadata that isn't the contents, but about the contents

Hagenek19:01:57

In JS I would just do todos[0].todos.push({id: 2, desc: "okdf"}). Yeah maybe I should just show you my real example

Hagenek19:01:24

(defn create-checklist [] { :id (.toString (java.util.UUID/randomUUID)) :sequenceMap [] ;Map<Integer, UUID> / Map of question number vs question UUID, to create sequences for each checklist / :answerMap [] ;Map<Integer, UUID> :expectedAnswerMap [] ;Map<Integer, UUID> :isSigned false })

noisesmith19:01:28

[] is never a map in clojure

Hagenek19:01:18

Im using a template from a kotlin programmer

noisesmith19:01:54

yeah, that doesn't really look like clojure code (though it would compile)

noisesmith19:01:26

you can combine swap! with update-in to do nested operations

👍 3
noisesmith19:01:15

ins)user=>  (def todos (atom {:id 0 :items []})) 
#'user/todos
(ins)user=> (swap! todos update-in [:items] conj {:what "homework" :when 0})
{:id 0, :items [{:what "homework", :when 0}]}

noisesmith19:01:26

this is closer to what you initially had

noisesmith19:01:59

but using an atom and def already implies a global / singular nature that doesn't line up with the particularity of having and id etc. at the top level

Hagenek19:01:17

Aha! I see.. I am using to simulate a database

Hagenek19:01:26

because I am creating mock data for a frontend

noisesmith19:01:33

right, that's the usual role of an atom

noisesmith19:01:00

on reflection my first example is closer to what you had, I misread it

noisesmith19:01:22

having a vector at the top level of an atom is relatively rare, usually you want keyed rather than indexed lookup

noisesmith19:01:33

but perhaps your db is actually a queue or stack

Hagenek19:01:54

Nono, it was a choice made without too much thought. I am coming straigth from javascript

Hagenek19:01:45

I have not chosen a db yet actually

noisesmith19:01:08

right, instead of [{:id 10 ... ...} {:id 20 ... ...}] we'd usually have {10 {... ...} 20 {... ...}}

noisesmith19:01:26

(or in your case actually the uuids you generate)

Hagenek19:01:37

Yeah of course, makes much more sense to have the UUID as a key to your other checklist data

noisesmith19:01:52

unlike js we only sometimes use hashes as "objects", and generally use them as databases

noisesmith19:01:38

so more like

(ins)user=>  (def todos (atom {})) 
#'user/todos
(ins)user=> (swap! todos assoc (java.util.UUID/randomUUID) {:what "homework"})
{#uuid "701b9c5d-6d61-45e6-bf81-8024953aa50d" {:what "homework"}}

noisesmith19:01:13

@georghagen a pattern you'll likely see is that most of the sort of code you'd usually write is replaced with a few combinations of collection operations in clojure code

Hagenek19:01:32

Yeah, that's what I really love about Clojure. I just have to get more experience to know my way around the collection operations! Thanks a lot for all the input, can't wait untill I can start contributing back to the clojure community myself 😃

🎉 3
roelof19:01:39

GN my friends. Hopefully I can work on exercism or 4clojure or my web project tomorrow

👋 9
roelof19:01:29

IS there a good free hosting platform for the website im building with compujure and ring ?

dgb2319:01:20

There are cloud providers who offer free tiers. The JVM is often supported. A simple provider would be Heroku, but bigger (and more complicated ones) like AWS, Azure and GCP provide free tiers as well as far as I know.

dgb2319:01:44

I don’t vouch for any of these, but it might be worth checking them out.

didibus06:01:23

You could try this: https://wiki.helionet.org/features/java You'd need to switch to https://github.com/marchrock/ring-tomcat-adapter I think. And I've never tried them.

didibus07:01:47

But generally, people use VPS hosting, which I do not know of any free options

didibus07:01:15

DigitalOcean, Linode, AWS, are popular options, they have things in the 5$ a month range.

didibus07:01:37

I think Heroku might have a free option for MVPs and personal projects, not sure

roelof09:01:54

thanks all

dgb2319:01:05

@georghagen You should maybe consider looking into the set data-structure and relational operations. Your collection looks like a set (since it has unique ids). You can use index to get a map from a unique key to each item in the collection, you can join sets arbitrarily, select on a predicate and so on.

noisesmith19:01:52

to me it doesn't look like a set - sets are about things that only stand for themselves and comparison of membership, this looks more like a mapping to me

noisesmith19:01:39

most of the data you'd put in a "todo list" entry doesn't stand for itself, and most todo list operations are not set operations

dgb2319:01:38

Ah right its a todo list. A simple map makes more sense there probably; KISS.

dgb2319:01:29

I’m currently diving deeper into logic (math and programming) and everything looks like a set to me…

Hagenek19:01:20

I think I confused you with the todolist example. Its a checklist, which will have questions attaches to it. these questions will have their own id and description and an id which denotes their relation to an answer

noisesmith20:01:25

is it an ordered thing for display?

dgb2320:01:47

there are specialized data structures for that

Hagenek20:01:29

I think thats the only thought behind having an integer tied to an UUID, to make sure everything is in the right sequence. But that could be implied in the datastructure. But if the client wants to be able to move it around, you might be in trouble? I don't know

noisesmith20:01:45

yeah, so the best idea is probably to separate the UI layer (stuff about ordering and labeling) from the relational layer (links between questions and answers) and the storage layer (indexing by id)

noisesmith20:01:36

clojure solutions to this sort of thing will definitely look unfamiliar coming from kotlin or js, and will often be specific to a particular UI tool (eg. reagent / re-frame)

Hagenek20:01:58

the frontend is being build with ionic in react

Hagenek20:01:08

I am creating the backend as a rest api

noisesmith20:01:23

OK, in that case we should probably go back to vector-of-maps (if there's only one global todo), or a map from session / user id to vector of maps

noisesmith20:01:50

and that's either in an atom (in memory) or an intermediate structure used when talking to a db

Hagenek20:01:32

That's right!

alexmiller20:01:48

If you are new to Clojure (or not!), we would love to have your feedback in the annual State of Clojure survey https://www.surveymonkey.com/r/clojure2021 - we've been doing this survey in the community for about a decade and it provides important information about trends and usage. If you're interested, last year's results can be found here: https://clojure.org/news/2020/02/20/state-of-clojure-2020

🏁 12
metal 3
👍 3
roelof20:01:51

Did already a few days ago

3
grazfather01:01:56

The questions ask whether you use it professionally, and then veer off and assume you do

alexmiller03:01:05

as it says at the top, just skip anything that doesn't apply

alexmiller03:01:11

only the first 2 questions are required

mathpunk23:01:36

I'm having trouble finding the clojure equivalent of lein deps, has anyone got it to hand?

mathpunk23:01:19

i'm dockerizing something and the recommendation from the clojure image is to download deps ahead of time, but the examples are lein-based

andy.fingerhut23:01:45

You want an option to clojure that will only try to load dependencies, and then stop?

dpsutton23:01:37

clojure -Spath should do the trick

andy.fingerhut23:01:47

A reasonably common way people deploy Clojure-based apps that avoids this question entirely is to create a JAR file that contains all of the code of your app and all of its dependencies, and then simply run java -cp ... command on the system where the application runs. Then that system doesn't need to be able to access repositories containing libraries at all.

noisesmith23:01:43

agreed with @U0CMVHBL2 that a jar is the simplest thing in the long run, depstar does this task well with the clojure tool https://github.com/seancorfield/depstar

seancorfield00:01:45

@U0E9KE222 If you're using an up-to-date clojure CLI (1.10.1.697 or later), you can use the -P option. See http://clojure.org/releases/tools

seancorfield00:01:13

That will just "prepare" things, i.e., download the dependencies you need and then not run anything.

alexmiller03:01:10

-P is the preferred way to do this

mathpunk16:01:16

thanks folks!