Fork me on GitHub
#beginners
<
2022-02-24
>
Marcelo Fernandes01:02:11

I'm reading Structure and Interpretation of Computer Programs and doing the examples in Clojure. I want to recreate this procedure which returns a tree scaled by a factor. I don't understand why (scale-tree 2 10) throws that Error

Marcelo Fernandes01:02:43

The procedure from the book

dpsutton01:02:00

the first branch calls (empty? tree). on (scale-tree 2 10) it is calling (empty? 2)

seqexp=> (empty? 2)
Execution error (IllegalArgumentException) at net.cgrand.seqexp/eval1715 (REPL:1173).
Don't know how to create ISeq from: java.lang.Long

dpsutton01:02:30

seqexp=> (doc empty?)
-------------------------
clojure.core/empty?
([coll])
  Returns true if coll has no items - same as (not (seq coll)).
  Please use the idiom (seq x) rather than (not (empty? x))
nil
This function expects a collection and 2 violates taht

Marcelo Fernandes01:02:12

right, my rubber duck debugging didn't work this time 🙂. Thanks!

👍 2
Marcelo Fernandes01:02:27

This works, however the first condition doesn't seem very elegant. Is there a way to improve the first condition?

Cora (she/her)03:02:28

you can always make another function that has a more intention-revealing name and use it there

👍 1
hiredman03:02:51

The next thing you might run into is using list? is usually a mistake in clojure, you want seq?

👍 1
dpsutton03:02:32

A while back i tried to do SICP in Clojure and it just really doesn't fit very well. I think just using chicken scheme, guile or MIT scheme is the best way to go

👍 2
Ngoc Khuat05:02:27

Off topic question: how many times did you learn SICP? I’m planning to do a second one in a couple of months 😄

dpsutton05:02:27

I've gone through it two or three times. But I never finish. But I still get the charm

gratitude 4
Nathan Rogers13:02:09

Might check out the series produced by code_report who did a series on SICP in Racket. Was a great way to follow along.

👍 1
quoll20:02:35

Sorry I’m a few days late… One of the (several) reasons for not working through SICP in Clojure is that it’s not idiomatic at all. For instance, Scheme is frequently looking for an empty list, while most Clojure functions that return an empty list will use nil punning instead. So it’s more common to use next rather than rest (for instance, this is what destructuring does… if you get to that part of Clojure). Consequently, a more idiomatic approach might be:

(defn scale-tree
  [tree factor]
  (cond (nil? tree) nil
        (not (sequential? tree)) (* tree factor)
        :else (cons (scale-tree (first tree) factor)
                    (scale-tree (next tree) factor))))
That works, but testing is something is nil and then returning nil if it is, it something that is usually done with when:
(defn scale-tree
  [tree factor]
  (when tree
    (cond (not (sequential? tree)) (* tree factor)
          :else (cons (scale-tree (first tree) factor)
                      (scale-tree (next tree) factor)))))
But that’s a cond with only 2 branches, which is better served with an if. This also means that the 2 conditions can switch places, and the not can be dropped:
(defn scale-tree
  [tree factor]
  (when tree
    (if (sequential? tree)
      (cons (scale-tree (first tree) factor)
            (scale-tree (next tree) factor))
      (* tree factor))))
But at this point, I’d probably take advantage of the if statement, and destructure once I know that it’s a sequential value:
(defn scale-tree
  [tree factor]
  (when tree
    (if-let [[f & r] (and (sequential? tree) (seq tree))]
      (cons (scale-tree f factor)
            (scale-tree r factor))
      (* tree factor))))
This looks totally different to the SICP version, even though it’s essentially doing the same thing! The reason for me going through all of this wasn’t to show off (honest!) but to provide an example of why you’re better to learn the principles of SICP in Scheme, and once you are familiar with that, bring those skills into Clojure. Trying to apply Clojure directly to SICP can be an interesting exercise, but it may set you up with poor expectations on how to write Clojure.

Marcelo Fernandes00:03:33

Thanks for the detailed response! I love these step by step on how to improve a piece of code. I took on @U03493LJW0H suggestions and I'm watching the code_report lessons as video is a format that I learn well too.

Hao Liang09:02:04

Hi. Where should I write docs in a defrecord form?

practicalli-johnny09:02:28

A line comment ;; would be the only option for writing docs for a defrecord. Alternatively, consider using a Clojure hash-map instead of a defrecord and add a clojure.spec if validation of values is required.

👍 1
seancorfield09:02:24

@lhing1112 defrecord does not support docstrings. It creates a Java type and those can't have metadata like docs.

Hao Liang09:02:41

Ok, thanks.👍

Adir Ohayon11:02:25

Hello everyone! I have a question regarding a function I wrote to check if something starts with quotes or not:

(defn another-func
  [val]
  (println "the val is:"val "and its type is: " (type val))
  (if (and clojure.string/starts-with? val "\""
           clojure.string/ends-with? val "\"")
    (println "true value")
    (println "false value")))
This is the only example I'd expect that will return true and it does (another-func "\"5\"") the val is: "5" and its type is: java.lang.String true value But, these also return true and I don't understand why, here are some examples:
(another-func 5)
the val is: 5 and its type is:  java.lang.Long
true value

(another-func "5")
the val is: 5 and its type is:  java.lang.String
true value

delaguardo11:02:51

You should add () if you want to call a function

delaguardo11:02:11

(starts-with? val "\"")

Adir Ohayon11:02:35

AAAHH, of course, thank you so much!

Adir Ohayon11:02:49

(also for the super fast reply, much appreciated!)

Nathan Rogers13:02:54

How opinionated is clojure in the "Pure" aspect of FP? In reading Clojure Applied, it seems IO and side effects are not rigidly restricted as in severely strict FP languages like Haskell. So to what degree does clojure attempt to be "Pure" outside of fundamentally immutable data structures?

andy.fingerhut14:02:01

Clojure is not Haskell. Clojure encourages and supports writing pure functions, and it is idiomatic, i.e. many functions in many widely-used libraries are pure functions. But there are no "pure function types" that are statically checked anywhere that require that one writes pure functions.

andy.fingerhut14:02:28

Unlike a language such as Java, Python, Ruby, JavaScript,where you have to go out of your way and against the grain of popular use to write pure functions.

Nathan Rogers13:02:12

Are there emacs/cider users, or are most people using VS Code or some other modern editor? I'm experiencing errors that cause Emacs to crash quite regularly, and that is something I've not experienced very often. Not sure what to make of it, whether its a cider issue, or something else.

Darin Douglass13:02:16

people generally use whatever they want, there are tools for each editor. the actual numbers are can usually be found on the state of clojure yearly survey. here are last year’s results (Q16 is about dev environment) https://www.surveymonkey.com/results/SM-S2L8NR6K9/

Darin Douglass13:02:46

if you have emacs questions, there’s an #emacs channel where you should be able to get some targeted help

Nathan Rogers14:02:06

I see, thanks Darin

practicalli-johnny14:02:32

Emacs has been used by the majority of Clojure developers as it has always had support from the early days. There are a wide range of editors that support Clojure https://practical.li/clojure/clojure-editors/

Matej Šarlija15:02:32

I use Prelude emacs (so Cider) for Clojure, works great with practically zero setup

Benjamin14:02:19

Can you improve my code?

(defn
  juxt-keys
  "Return a new map, potentially with new keys added.
  `juxtf` is called for each kvp and can return
  either nil or a new kvp that will be 'assoc'ed into m."
  [m juxtf]
  (into
   {}
   (comp
    (mapcat (juxt identity juxtf))
    (remove nil?))
   m))

;; use case

(comment

  (let [timestamp-key? #{:fo}]
    (juxt-keys
     {:fo 1645712055012}
     (fn [[k v]]
       (when (timestamp-key? k)
         [(keyword (str k "-iso"))
          (.toString (java.time.Instant/ofEpochMilli v))]))))

  {:fo 1645712055012, ::fo-iso "2022-02-24T14:14:15.012Z"})

dpsutton15:02:46

the remove nil sounds incredibly dangerous

ghadi17:02:24

(remove nil?) isn't even necessary

👍 1
ghadi17:02:43

(conj {} nil) = {}

Matej Šarlija15:02:44

(map #(.getName %) (.listFiles (File. ".")))
in this example, can't getName be "bare", I mean why do I need to wrap a function into an anonymous function, type inference?

Ferdinand Beyer16:02:25

map requires a Clojure function, e.g. something that implements the IFn interface. getName, the method on Java’s File class, is not a function for Clojure

Ferdinand Beyer16:02:26

The .getName is actually syntactic sugar for the “dot” special form for java-interop. Think of getName as an argument for .

Alex Miller (Clojure team)16:02:43

(this is an area we are looking at for Clojure 1.12)

Matej Šarlija16:02:50

grateful for the input 🙏

Grant Horner18:02:58

@U064X3EF3 I know this is such a little thing, but I run into it so often when doing java interop-y work. I’m so glad this is being looked into

mbjarland19:02:35

@U064X3EF3...since I already asked once : ) and I've been mulling over how such an implementation would actually work. Is the clojure team looking at actually making it so that java methods will work as is or is there some other solution under consideration. And I understand nothing is cut in stone, just curious what direction the investigation is going...

Alex Miller (Clojure team)19:02:12

not going to go into it here, it's more complicated than it appears, and is adjacent to other things we're considering.

mbjarland11:02:40

ok fair enough. Look forward to whatever the end result ends up being.

quoll20:02:09

Remembers the halcyon days of memfn