This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-10-29
Channels
- # architecture (3)
- # aws-lambda (1)
- # babashka (7)
- # beginners (60)
- # calva (33)
- # chlorine-clover (8)
- # cider (24)
- # circleci (4)
- # clj-kondo (13)
- # cljs-dev (1)
- # cljsrn (12)
- # clojure (76)
- # clojure-australia (6)
- # clojure-europe (100)
- # clojure-france (1)
- # clojure-nl (13)
- # clojure-uk (16)
- # clojurescript (49)
- # conjure (1)
- # cryogen (8)
- # datomic (43)
- # dirac (3)
- # etaoin (1)
- # exercism (4)
- # fulcro (32)
- # jobs (2)
- # kaocha (4)
- # nginx (1)
- # off-topic (106)
- # pathom (8)
- # reagent (5)
- # reitit (5)
- # sci (52)
- # shadow-cljs (37)
- # tools-deps (30)
- # tree-sitter (18)
- # xtdb (18)
Iām in a reflective mood today, good morning
whoās there?
I don't think so tho. I remember how offended everyone in c.l.l was about a lisp on the jvm that betrayed the lisp machine ideals
a good lisp on the jvm with good interop so that I could use the java libs I already knew? Fantastic.
For me Clojure was a way to learn about the JVM. I was only familiar with .NET and didn't like what I saw comparing the built-in date library and some more.
But I needed to work with the JVM because I was a lecturer back then and these students were mostly taught Java
Within a year or two I was running a Clojure course to teach them some next level programming :P
I was lucky to avoid teaching OOP design patterns because I couldn't comfortably teach that while knowing the alternatives
I like it as an intellectual thing, but I don't like it from the perspective of writing boilerplate in a language you have to work towards freedom first
oh gods, the way people abused it was terrible, but actually understanding the patterns made it easier to think your way around a problem without having to invent everything yourself
yeah, you could apply some of those patterns in Clojure as well, but most of it is just plain common sense don't you think
I think they are often common sense, but that is why programmers would never know them š
but for implementing fp languages in OOP languages (like clojure in java) I think they are probably pretty useful, just don't see them as things you can copy and paste from
yeah, maybe put it too strongly, but these books of hundreds of pages of material which would almost be trivial in Lisp/FP, it didn't sit well with me
Now that I'm ranting anyway: I wish core wouldn't have used the 2-arity of map, filter etc for transducer. I built a clj-kondo type system for it to reduce the amounts of errors I get because of mistakes I make with this.
the whole transduce thing of ⢠arity 0 for initialise ⢠arity 1 for finish ⢠arity 2 for iteration seems like a really strange design choice to me
it would certainly be clearer to declare as a record
the reason I can think of is that JVM is massively optimised for overloaded functions, and this was felt to outweight the confusing code implications?
Is there are more compelling reason that I have missed?
(Er is that what you were talking about...? Or I have missed the point?)
I would have preferred (mapping inc)
as forgetting to pass args to (map inc)
is often just a mistake in my code
I had another one right now: there was a JSON payload that silently missed a field, turns out it was because a transducer could not be serialized, yeah, duh...\
I would have preferred (mapping inc)
, (filtering odd?)
over (map inc)
and (filter inc)
any time of day
@borkdude thx for clj-kondo. It is making my refactoring to get around not being able to debug things a lot easier. Couldn't get through this w/o your tool.
Hereās a couple of linters Iād like (but theyād certainly be controversial):
1. Prefer partial
over #(..)
when all youār doing is binding variables
2. Prefer named (fn foo [ā¦] ā¦)
over #(ā¦)
and certainly over (fn [ā¦]ā¦)
3. Flag #(..)
or (fn foo [ā¦] ā¦)
spanning multiple forms/lines
I guess 1) is hard, since you have to analyze the #(..)
to understand that youāre just ācurryingā. 2) would be simple enough, warn/error on #(...)
or (fn [..] ..)
3) should probably be doableā¦
I wouldn't know why one would prefer partial over #(..) or (fn [...]), seems a bit random to me + you will lose reloading for vars you bind over with partial
3 seems a bit arbitrary to me. 2. could implement, but not sure anyone would adopt this as a linter and adhere to it strictly in their code base. As an example, I recently implemented the shadowed-var linter. In theory very useful, but after using it a bunch, I found it too restrictive myself
It might be a principle somewhere around it, but itās basically that when I see partial
I know that there is nothing more going on than the binding of variables. If I see #(..)
I have to read the form to se e that nothing else is going on.
Iām not at all asking you to implement, but it might be fun for me to try to implement at some point.
implementing is one thing, maintaining is another one. if something is wrong with a linter, usually I'm the one ending up fixing it, even if some other person wrote it (unfortunately). my point was more like: what's the point of implementing a linter and maintaining it indefinitely if it's too strict for anyone to use
As for 3) we have examples in our codebase of
(reduce (fn [acc x]
;; 300 lines of code
) {} lol)
clj-kondo has this, it's called hooks. you can use it today. docs: https://github.com/borkdude/clj-kondo/blob/master/doc/hooks.md examples: https://github.com/clj-kondo/config
Right, so I can basically figure out how to write my own linter, stick it in a hook and voila presto?
what you can do with a hook is transform the shape of a call and do some linting on the original shape of the call
The thing with partial
is somewhat related a thing I first heard on the Idealcast with Gene Kim and Michael Nygaard. Then I heard it again on an episode of the Corecursive podcast.
The thing in the podcasts was something like āThe more abstract the types of the parameters of the function, the less the function can doā
So if you have a fn signature fn T t => T
(sorry about the syntax) where T
is some type, then this fn can only do one thing.
if you have a fn signature fn Int i => Int
you have a function that can do an infinite amount of things
So if you have (map (partial ...) foos)
you know that youāre only binding parameters, whereas if you have
(map #(ā¦.) foos)
anything can happen.
I don't really see the improvement. (partial foobar 3)
or #(foobar 3 %)
convey the same information and don't guarantee anything about potential side effects
partial returns a closure. e.g. (partial foobar x)
will capture x at one point in time. any changes to x
won't be seen in that closure. which prohibits REPL-driven development.
My point is that the moment I see partial
I can almost ignore the rest, but I have to read #(ā¦)
to the end to see that there is nothing else going on.
I'm not saying: always use this or that, but a linter will say that and this is where I see not many people using it in the long run
not if foo
is from another namespace, unless you're into the entire REPL reloaded workflow
Good night
I don't like partial because it obscures the arguments expected by the new function you've created. I've ended up in a muddle because of that many times.
when you choose partial
you are signalling that you expect at least two different arities, and that is a feature,
seems to me