Fork me on GitHub
#beginners
<
2021-09-29
>
Redbeardy McGee01:09:22

I'm learning clojure, and found a couple of sites with exercises supporting clojure. I feel like an idiot for spending a day digging through clj docs trying to find the most applicable core language features for the very first exercise, but I am definitely missing something. The problem statement: Write a program that prints the temperature closest to 0 among input data. If two numbers are equally close to zero, positive integer has to be considered closest to zero (for instance, if the temperatures are -5 and 5, then display 5). Display 0 (zero) if no temperatures are provided. I can get the value closest to zero this way: (reduce min (map #(Math/abs %) temperatures)) I can probably use (if (seq temperatures) (min ...) 0) to guard against an empty seq. However, this approach loses all negative values, which results in returning a positive integer even when all inputs are below zero. Does this mean I've tried to approach the problem incorrectly, or am I missing a functional idiom that could preserve the negative sign?

Duane Bester01:09:08

just messing around:

(def temps [-7 5 4 -8 9 -3 2])
(if (empty? temps) 0
  (last (first (sort (zipmap (map (partial Math/abs) temps) temps)))))

Duane Bester01:09:42

made the abs vals the keys of a map & the values the temps, then sort the map, then grab first key,val pair, then the val

hiredman01:09:07

Math/abs being a java method is not something you can call partial on, you must using clojurescript?

hiredman01:09:28

The correct solution is very obviously a fold

Redbeardy McGee01:09:42

That's an interesting direction to go in. Thanks for the neat idea.

hiredman01:09:01

(reduce min ...) Is almost perfect

hiredman01:09:30

But min is operating with the default ordering of numbers

Redbeardy McGee01:09:53

I'm trying to "think functionally" for the solution which is why I arrived at the reduce, but I got stuck after that.

hiredman01:09:14

(reduce my-min identity-value ...)

hiredman01:09:20

Is what you want

hiredman01:09:50

Where my-min and identity value are monoid over your values

Redbeardy McGee01:09:16

I haven't grokked monoid yet

hiredman01:09:30

The link about comparators should be good fodder for writing my-min if not exactly how to do that

hiredman01:09:46

A monoid is not a monad to be clear

Redbeardy McGee01:09:00

It's okay, I don't understand either yet 🙂

hiredman01:09:40

A monoid is a function that is really useful with reduce

hiredman01:09:29

+ has an identity 0 (x+0=x)

hiredman01:09:23

So you can write (reduce + 0 [1 2 3]) and it will work

Redbeardy McGee01:09:03

I remember reading a bit about that.

hiredman01:09:04

For min, the identity value is something like positive infinity, I often use Long/MAX_VALUE

hiredman01:09:07

The min of x and Long/MAX_VALUE is in practice often x

Redbeardy McGee01:09:08

Right, of course

Duane Bester01:09:23

cljs version

(if (empty? temps) 0 (last (first (sort (zipmap (map #(.abs js/Math %) temps) temps)))))

hiredman01:09:37

With a complexity of n^4 or something

hiredman01:09:54

Where a fold will do it in n

hiredman01:09:47

(sorting is n^2, zipmap is n, last is n)

Duane Bester02:09:55

yeah the java sort is O(n log n). agreed there is a more efficient approach

Redbeardy McGee05:09:55

finally sat down to finish this and find out that this platform has broken boilerplate code throwing a NumberFormatException by trying to call Integer/parseInt on the empty string.

Redbeardy McGee01:09:57

Sorry, I don't understand what the implication of this link is meant to be.

hiredman01:09:09

You are comparing numbers with certain rules, the tool for comparing things are comparators, which is what that link is about. If you have a comparator that uses the order defined by your rules, then you can use it lots of other things

hiredman01:09:38

sort, sorted-set, etc as mentioned in that link

sova-soars-the-sora05:09:13

comparators ... i feel like that's a deeper java thing, maybe a few clear examples of wtf reduce is doing would be better for a beginner sure, you'll eventually want to make your own comparator but what am reduce? reduce is a magic fairy with 2 hands that starts with an initial value, does some operation on initial value and first value, and then stores the result as the new initial value, and "collapses" a collection two elements at a time until it's reduced to one value

Redbeardy McGee06:09:06

Thanks, I get what reduce does, though I struggle to know when it's appropriate to apply

raspasov08:09:13

reduce is appropriate whenever you need to “visit” every element of a collection in order to obtain a result. In practice, I don’t usually use it very often. Depends on the type of problems you’re solving. Often, there can be a simpler way to solve a problem via map and/or filter if you’re processing items one by one. But if you really need to check every item, reduce is your friend.

raspasov08:09:01

If you need to go through every item of a collection in order to produce a side effect (send items to analytics, save to a database, etc), there’s run!

raspasov08:09:52

Which is basically reduce 🙂

(defn run!
  "Runs the supplied procedure (via reduce), for purposes of side
  effects, on successive items in the collection. Returns nil"
  [proc coll]
  (reduce #(proc %2) nil coll)
  nil)

4
😄 4
Redbeardy McGee06:09:17

seemed perfect for this case

Benjamin08:09:22

(defn assoc-email [m]
  (assoc m :email (fetch-email (:user-id m))))
what would be an unsurprising name for this aasoc-email function?

delaguardo08:09:48

ensure-email ?

👍 1
pavlosmelissinos09:09:48

My 2 cents: I probably wouldn't put it in a function on its own. Assuming that there are other keys with trivial logic like this that you want to assoc to m, I'd rather gather them all into an enrich-m function. Also what does m represent? Something more specific, like user would communicate intent better.

Johan Thorén09:09:47

@UEQPKG7HQ, I used to think that user would be a better name, but the way I interpret https://guide.clojure.style/#idiomatic-names, it seems m would be preferred?

pavlosmelissinos09:09:32

@U02DW53HCSE I don't think that the style guide suggests that it's idiomatic to name every map m. m implies an arbitrary map. If you're writing a function that operates on generic data structures, it's idiomatic, sure. But in this particular example, m represents an entity that has an email (from the context it seems like it's a user but I can't be sure). And yes, you can't tell if user is supposed to be a map or a collection or something else but that's what spec is for.

didibus11:09:58

You should try to write your code in a pure fashion. Do this instead:

(defn assoc-email [m email]
   (assoc m :email email))
Now the name you have makes sense. And in a higher level function you'd now coordinate the two:
(->> (:user-id user)
     (fetch-email db)
     (assoc-email user))

👍 3
sova-soars-the-sora04:10:08

Argument order anyone? Could also use {:keys [m email]}

zugnush09:09:19

I'm really struggling with a java interop problem. I think because there are multiple implementations of an interface. I'm trying to use https://github.com/bbottema/rtf-to-html.

zugnush09:09:52

(ns zugnush.rtf2html
  (:gen-class)
  (:import
   [org.bbottema.rtftohtml.impl RTF2HTMLConverterJEditorPane]
   [org.bbottema.rtftohtml RTF2HTMLConverter]))

(.rtf2html (RTF2HTMLConverterJEditorPane.) "xx")
(.rtf2html (RTF2HTMLConverter.) "xx")
;; both no matching ctor

(RTF2HTMLConverter/rtf2html "xx")
(RTF2HTMLConverterJEditorPane/rtf2html "xx")
;; both no matching method

dpsutton10:09:57

there are no public constructors, just a public static instance for you to use https://github.com/bbottema/rtf-to-html/blob/master/src/main/java/org/bbottema/rtftohtml/impl/RTF2HTMLConverterJEditorPane.java#L20-L22. It should be something like (.rtf2html RTF2HTMLConverterJEditorPane/INSTANCE "the rich text")

dpsutton10:09:34

as in the readme:

RTF2HTMLConverter converter = RTF2HTMLConverterJEditorPane.INSTANCE;
RTF2HTMLConverter converter = RTF2HTMLConverterClassic.INSTANCE;
RTF2HTMLConverter converter = RTF2HTMLConverterRFCCompliant.INSTANCE;

String html = converter.rtf2html("RTF text");

zugnush10:09:55

Thanks. My java's not good.

Jakub Zika12:09:07

I am coming from the same place - nearly no Java experience. I recommend you to find some really basic Java tutorial and try to do it in Clojure. It will help you to get basic Clojure / java interop really quick.

Lycheese11:09:41

Is there a way for an anonymous function to reference itself to make multiarity nicer? (I'm trying to have an overloaded re-frame event that allows optional arguments. I'm currently doing this with a rest argument and just fishing out the relevant information from a map I supply there, but if possible I'd like to make it less janky.) e.g. what would ? be here? (fn ([some opts] (? some opts default-parameter)) ([some opts optional-argument] (…)))

delaguardo11:09:51

anonymouse functions can have a name (fn foo [] …) and this name resolvable within functions’s body

(fn foo
  ([] (foo nil))
  ([arg] ...))

dpsutton11:09:22

anonymous functions by definition do not have names 🙂 . but you can name a function expression

Lycheese11:09:22

Ah I didn't know that. Thank you very much both of you :)

dpsutton11:09:52

(let [f (fn give-it-a-name
          ([] (give-it-a-name :foo))
          ([arg] arg))]
  (f))
:foo

Benjamin17:09:44

is it fine to create a core.async go routine that ends up being blocked by a parking take forever? Or should these be closed?

(a/go-loop
      []
      (let [thing (a/<! c)]
        ...)
      (recur))
and c at some point just doesn't deliver anything anymore

ghadi17:09:47

if you know that input is done, that channel's producer should close it

Benjamin17:09:11

I see. I guess it is a common pattern to have a coll, (say a list of users) and fetching for each element something, in parallel. So I get a list of say emails or sth. Is this what pipeline is for?

ghadi17:09:10

generally yes - you can look at pipeline-async

ghadi17:09:49

but it's not tying up a thread while waiting

NoahTheDuke19:09:34

in the clojure core function select-keys , what does (. clojure.lang.RT (find map (first keys))) do?

NoahTheDuke19:09:56

or more specifically, what does the (. clojure.lang.RT part do? the rest looks like a normal function call

Apple19:09:14

java interop

Apple19:09:24

runtime probably

Apple19:09:36

(. clojure.lang.RT (find map (first keys)))
can be just
(find map (first keys))
cause i see in source code that find is now defined as a clj func.

NoahTheDuke19:09:30

right, that’s part of what’s confused me. i’m not sure what it’s doing in the algorithm

Apple19:09:11

source code is there if you want to look it up the algo.

NoahTheDuke19:09:24

same with the find definition, why the call to clojure.lang.RT?

Apple19:09:59

find was probably added later.

NoahTheDuke19:09:43

ah, excellent. thanks for finding that

noisesmith16:09:05

also the idiomatic interop syntax would usually be:

(clojure.lang.RT/find map (first keys))

👍 1
noisesmith16:09:25

(that expands to the (. class static-method ...) version used here)

alexmiller19:09:03

It is just a function call to the RT class, which is the internal Clojure runtime

👍 1
NoahTheDuke19:09:55

if I’m implementing my own version of select-keys that will skip nil values, do I need it? i would have written the line without that part

George Silva20:09:18

Hello dear friends! I have been doing https://exercism.org/tracks/clojure/exercises/elyses-destructured-enchantments exercise on exercism. The last exercise on destructuring made me sweat a bit. It is passing, but I was wondering if there is a way to be less verbose on insert-face-cards. The tricky part is a test that checked if we were return nil as the first element, when passed an empty vector to insert-face-cards. I came up with the filter, but still a bit puzzled.

(ns elyses-destructured-enchantments)


(defn first-card [deck]
  (let [[top-card] deck]
    top-card))

(defn second-card
  "Returns the second card from deck."
  [deck]
  (let [[top sec] deck]
    sec))

(defn swap-top-two-cards
  "Returns the deck with first two items reversed."
  [deck]
  (let [[a b & rest] deck]
    (concat [b a] rest)))

(defn discard-top-card
  "Returns a vector containing the first card and
   a vector of the remaining cards in the deck."
  [deck]
  (let [[a & rest] deck]
    (vector a rest)))

(def face-cards
  ["jack" "queen" "king"])

(defn insert-face-cards
  "Returns the deck with face cards between its head and tail."
  [deck]
  (let [[a & others] deck]
    (filter some? (into [a] (concat face-cards others)))))

bhenry20:09:55

you'll need the filter to lose the nils but you don't need into since you're already using concat

user=> (concat [1] ["a" "b" "c"] [2 3 4])
(1 "a" "b" "c" 2 3 4)
user=> (concat '(1) '("a" "b" "c") '(2 3 4))
(1 "a" "b" "c" 2 3 4)

George Silva14:09:48

Thank you both for the suggestions! 🙂

Ed17:09:09

Is the a the only thing that's actually going to put a nil in the results of insert-face-cards? If so you might also be able to write that as (concat (when a [a]) face-cards others)

1