This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-05-12
Channels
- # announcements (1)
- # babashka (42)
- # beginners (114)
- # bristol-clojurians (2)
- # calva (7)
- # cider (4)
- # clj-kondo (7)
- # cljs-dev (37)
- # cljsrn (13)
- # clojure (114)
- # clojure-austin (3)
- # clojure-europe (5)
- # clojure-nl (10)
- # clojure-spec (77)
- # clojure-sweden (4)
- # clojure-uk (16)
- # clojurescript (52)
- # conjure (155)
- # core-async (18)
- # cursive (23)
- # datomic (20)
- # duct (2)
- # emacs (13)
- # figwheel (3)
- # figwheel-main (9)
- # fulcro (31)
- # gis (8)
- # helix (33)
- # jobs (12)
- # jobs-discuss (66)
- # kaocha (4)
- # lein-figwheel (1)
- # meander (16)
- # off-topic (5)
- # pathom (13)
- # pedestal (6)
- # quil (6)
- # rdf (17)
- # re-frame (32)
- # reagent (34)
- # reitit (30)
- # remote-jobs (1)
- # ring (2)
- # shadow-cljs (149)
- # spacemacs (1)
- # sql (8)
- # tools-deps (90)
- # xtdb (19)
sorry if this is a very cursed question, but I want to learn clojure (and functional programming in general) and wanted to do it by implementing algorithms. Is it possible to implement basic sorts (selection sort, etc,) in clojure in a functional way, instead of imperative? Or is that just very cursed way of learning/doing clojure/functional programming
Merge sort is very amenable to functional style implementation. Quicksort as well. What both have in common is that they can be thought of as dealing with manipulating sequences, and sub-sequences of the input sequence, versus mutating an array. Quicksort is also typically presented as how to implement it while mutating the elements of an array that holds the input sequence to be sorted. That can be done in Clojure in mutating style, too, if you wish, but it would be the traditional imperative "style" of implementation.
If you are hyper-concerned about performance, then I doubt you will get the best performance from most functional style implementations that avoid mutation. If you are interested because of learning functional style programming, and not mainly concerned with performance, then go for it.
Clojure seems to encourage very loooong lines. I do like to keep my lines down to 80 columns, but when using if only a single expression per branch is allowed. How would you break up the last line of the block below? I am not smart enough to keep track of all that goes on in the last line
(if (empty? todays_migrations)
(string/join "-" [tstr "001"])
(string/join "-" [tstr (format "%03d" (+ 1 (apply max (map #(Integer/parseInt (subs % 11 14)) todays_migrations))))]))
you could indent it wildly
(if (empty? todays_migrations)
(string/join "-" [tstr "001"])
(string/join "-" [tstr
(format
"%03d"
(+ 1
(apply max
(map
#(Integer/parseInt (subs % 11 14))
todays_migrations))))]))
or just cut it up and make it pretty!
(defn max-migration
[migrations]
(->> migrations
(map #(Integer/parseInt (subs % 11 14)))
(apply max)))
(if (empty? todays_migrations)
(string/join "-" [tstr "001"])
(string/join "-" [tstr
(format "%03d" (inc (max-migration todays_migrations)))]))
or start the whole thing with (string/join "-" [tstr
and move/adjust the if portion appropriately to be right after tstr
fwiw, i also try to stay within 80 columns -- one nice side-effect is that side-by-side windows comparisons are way easier
(defn extract-integer
[s]
(-> (subs s 11 14)
Integer/parseInt))
(defn get-max-migration-number
[coll]
(max (map extract-integer) coll))
(defn format-migration-number
[n]
(format "%03d" n))
(if (empty? todays_migrations)
(string/join "-" [tstr "001"])
(string/join "-" [tstr (format-migration-number
(inc (get-max-migration-number todays_migrations)))]))
This is my preferred style--break out the expressions into their own functions and give each function a descriptive name. This has the added advantage of making your code essentially self-documenting, without the use of comments.
i like to go more in that direction once i'm happier that my code is likely to stay around.
I never nest things as deeply as the original example, my first version of it would use let instead of nesting
(if (empty? todays_migrations)
(string/join "-" [tstr "001"])
(let [max-val (apply max
(map #(Integer/parseInt (subs % 11 14))
todays_migrations))]
(string/join "-" [tstr (format "%03d" (inc max-val))])))
I would say you really want to use let in order to establish some intermediary bindings, the way noisesmith shows with max-val. What this does is allow you to make sense of what you are doing by giving a descriptive identifier to it. You don't want to have to mentally parse that long line, so pull the transformation out and either name it as a function that you call, or put it in a let that appears earlier.
(let [nxt-id (inc (apply max
(map (fn [s]
(Long/parseLong (subs s 11 14)))
todays_migrations)))]
(if (empty? todays_migrations)
(str/join "-"
[tstr "001"])
(str/join "-"
[tstr
(format "%03d" nxt-id)])))
I think calculating nxt-id might error if todays_migrations is empty (also see the thread above)
tl;dr - pls don’t make long lines. In the worst case, it devolves to manual paren counting. Leverage indentation.
@noisesmith yeah your solution is better
we posted our let blocks at almost exactly the same time :D
@potetm the error I would foresee:
scratch=> (apply max [])
Execution error (ArityException) at scratch/eval28493 (REPL:1).
Wrong number of args (0) passed to: clojure.core/max
(let [nxt-id (inc (reduce max
0
(map (fn [s]
(Long/parseLong (subs s 11 14)))
todays_migrations)))]
(str/join "-"
[tstr
(format "%03d" nxt-id)]))
@U012GN57FJ9 My official recommendation 😄
This is perfect, thank you
FYI - If you get used to formatting things like this, you start to be able to glance at code w/o having to read every word. It’s really nice.
most clojure functions behave well when used with apply and empty colls, max isn't one of them
there's also (apply max 0 coll)
yeah, max is implemented using reduce1 which is less optimized
sometimes I think that people use some instinctual "line count = complexity" metric to end up with 20 forms nested on one line
I think it's actually more like complexity = line-count^expressions-per-line
When you properly lisp-indent things, your ability to scan increases at least 10-fold.
I think operations-per-line is a better metric than character count, but it's harder for an editor to show as a vertical bar
just count the left parens :)
Yeah, I prefer >80 chars per line to allow for really verbose var names
I think Kevlin Henny put it well, "this line is so long it has a different weather system on the other end"
And then it's compounded by arbitrary indentation, with all the arguments stacked in a column on the far right hand of the screen
I know it’s not your point, but I wanna point out that this formatting isn’t arbitrary – it’s by design. Stacking arguments tells you something about what’s going on!
What I meant by arbitrary about the indentation is the difference between these two:
(foo-bar-bazz-quux-controler-proxy-factory arg1
arg2
arg3
arg4)
(foo-bar-bazz-quux-controler-proxy-factory
arg1
arg2
arg3
arg4)
People align their code (information) in a terrible manner because it's facilitated by their tools (screen, IDEs), not because it's human readable or conveys information better. The second form is way more readable in my opinion
but the first is the default – most things should fall into that when the line isn’t too long
Exactly. If I have to scroll right on my laptop screen in a maximized buffer only to see the arguments there has been a serious failure in the physical design of the code's layout, which makes me wonder what other weird ideas the programmer had. Which would you say you prefer, one of the first two or this third option?
(foo-bar-bazz-quux-controler-proxy-factory
arg1 arg2 arg3 arg4)
The biggest problem I find with the first, it being a default only making things sadder, is that variable names can also explode in size, making attention drift across the screen. Turns out we don't read in long lines, but in tight columns. Look at all the websites designed towards consuming text, they all lay it out in the center in a rather narrow column (facbook, twitter, blogging platforms, news sites, etc.)
(defrecord TakeALotOfParams [arg1 arg2 arg3
other-arg1 other-arg2 arg5 arg6
my-arg my-other-arg1])
I have a problem with crud GUIs where I frequently lose track of row identity in wide tables, my monitor is 4k but that's not going to help me read a line that's as wide as my screen
Clojure converts collections to sequences but not vice-versa. To convert it back to desired collection (i.e. using into
) does that mean you’d be iterating over the items twice? Not sure if there’s a special way conversions are handled under the hood, just wasn’t intuitive at first that map
, for example, would return a sequence rather than original collection
there are several other options depending on what kind of tradeoffs you want for:
• being concise
• being fast
• clarity
• generality
typically, if I want a vector result, I use mapv
. if I want the result to be a map, I use something like (into {} (map f) my-map)
.
edit: using @noisesmith ‘s suggestion
by moving the first )
over to the left, you can avoid creating a redundant lazy-seq
that links is helpful, thanks! Reading Programming Clojure as we speak as well (which is where question came from) 🙂
I'm using a 3rd library function (feedparser_clj/parse-feed) to return (what looks like) a resulting hashmap. From reading the source, parse-feed
returns a map from a defrecord
map->SomeRecordObject
. Doing (str resultingmap)
gives me "feedparser_clj.core.feed@c749ab8d"
but not the actual hashmap I want. If I evaluate resultingmap
, I do get some output starting like this #feedparser_clj.core.feed{<hashmap of the content i want>}
How do I just get the resulting hashmap?
that object is a map, you can do most things to it that you can do to a map
if you really need it to be a hash map, call (into {} resultingmap)
right, and records are a special category of maps - unlike maps they can have custom methods
in general, in clojure #foo[...]
#foo "..."
#foo{... ...}
etc. are custom readers, they consume the folowing thing (usually a hash-map, vector, or string) and produce a custom type
@watchtheblur also if youcall (defrecord Foo [...] ...)
clojure automatically creates the functions ->Foo
and map->Foo
as constructors for creating new instances
(as well as defining a reader tag, and using that reader tag when displaying the data by default)
I was reading this page (https://clojure.org/reference/reader) to understand more about readers, but couldn't find the relevant section for custom readers. Not sure if I was on the right page?
@watchtheblur see here https://clojure.org/reference/reader#tagged_literals - it doesn't mention the readers created when you define new records, but they work in a similar way
Thanks @noisesmith! I'll take a look. Much appreciated!
@watchtheblur they are pretty simple once you learn a few rules
scratch=> (defrecord Foo [])
scratch.Foo
scratch=> (map->Foo {:a 0 :b 1})
#scratch.Foo{:a 0, :b 1}
scratch=> (:a (map->Foo {:a 0 :b 1}))
0
scratch=> #scratch.Foo{:x 2}
#scratch.Foo{:x 2}
It's just odd that (str (map->Foo {:a 0 :b 1}))
would give the object reference rather than the map?
str never returns a map
str on a clojure hash-map happens to be the same as pr-str, for records that's not true, and if you don't implement toString you get clojure's default object->string printer
pr-str is the method that tries to print a string that you could read
and get something =
scratch=> (str #scratch.Foo{:x 2})
"scratch.Foo@3c6f116a"
scratch=> (pr-str #scratch.Foo{:x 2})
"#scratch.Foo{:x 2}"
there are many cases where pr-str is much better than str - another classic example:
scratch=> (str (map inc (range 10)))
"clojure.lang.LazySeq@c5d38b66"
scratch=> (pr-str (map inc (range 10)))
"(1 2 3 4 5 6 7 8 9 10)"
Ah, I see that I should've gone further and searched for the default reader for the object I was using. pr
and pr-str
points out that they produce output that can be read by the reader
Is there any existing page that gives a quick comparison/description of the differences between Clojure's different printing/convert-to-a-string options?
e.g. a table that has a bunch of different types of values/objects in a column on the left, and separate columns showing what str
, pr-str
, etc. will do with them?
no, and it's pretty complicated
in general, "pr" things print as readable data, "print" things print for humans, "str" things make strings
the reality (esp once you get into pprint) is way more subtle
there's also not one answer for most of those due to the number of dynamic flags that can be in play
Understood. Having such a table, one that included some of the flags like *print-readably*
would be nice as a quick reference. I may consider creating such a table and publish it some time. Those kinds of things I have never successfully committed to memory before.
there are actually some docs written by Tom Faulhaber buried in the api docs
https://clojure.github.io/clojure/doc/clojure/pprint/PrettyPrinting.html
(and also https://clojure.github.io/clojure/doc/clojure/pprint/CommonLispFormat.html about cl-format)
these are very old, dating back to the original introduction of pprint and cl-format
I'd love to have modern versions of such things to put on the site
https://twitter.com/WomenWhoCode/status/1259947685131571200?s=09 One of the most paid language and it's also funny 😁
(probably best to direct any follow-up discussion to #jobs-discuss so it doesn't distract from beginners getting answers to their Clojure questions here)
It’s not strictly a job discussion that’s underneath this link. What this image hides is the fact that the same survey discloses you need about 7+ years of experience to get any of these salaries. And also that females on average have fewer years of experience compared to males. So again, to me this confirms that there are too few women in technology work. I am hoping this gets better in the coming years.
I specifically notices the gender related details due to the twitter account that mentioned this image. It is an account advocating women to work in tech.
As an advocate of more women being in Tech - esp. the UK this is interesting however agree with @U04V70XH6 this for another forum please
1st Learning a new language is gender free, so this post is for everyone. 2nd a beginner can be motivated, not to give up, also by the fact that this language open up higher paid jobs. So read it as a motivational post for beginners and not as job advert. This is why I have posted it here.