clojure-uk

A place for people in the UK, near the UK, visiting the UK, planning to visit the UK or just vaguely interested to randomly chat about things (often vi and emacs, occasionally clojure). More general the #ldnclj
dharrigan 2021-02-18T04:37:41.350Z

Ugh. Early mornings...

dharrigan 2021-02-18T05:09:07.350600Z

So, I overheard someone the other day express this statement "Most [experienced] clojure devs, if asked, would say that if Clojure was redone, laziness would not feature [given that mostly everything is eager]" I'm intrigued by this. As a relative newcomer to Clojure I have no opinions on this, but perhaps others might?

dharrigan 2021-02-18T05:13:08.352200Z

Does laziness still make sense? (given that a lot of newcomers, including myself, have been (and continue to do so from time to time) bitten by the laziness "bug", i.e., works in the repl, but not when "running", as the repl is eager...)

2021-02-18T11:46:58.373600Z

Yes laziness still makes sense, and I’d also strongly disagree with the assertion that laziness wouldn’t feature if clojure was redone. As @dominicm says transducers would just be more prominent. Also clojure has never cared for what newcomers think 🙂 it’s been designed to cater for experienced developers. laziness has a big advantage/disadvantage (trade off) over eagerness (even in the form of transducers) — which is that lazy seqs cache their values. Indeed this caching is I think the main issue/feature with laziness. This means you can return to previously seen values and guarantee a stable/repeated result. This can also make working with lazy seqs easier in development, as you can capture and inspect them more easily, but if you capture a transduction over some I/O stream it will be gone when you next look at it. You’ll still be able to capture any individual value, but not the whole sequence of results (without more work). The disadvantages of this are of course well known.

dharrigan 2021-02-18T11:48:44.374Z

Also clojure has never cared for what newcomers think is a pretty strong statement. All languages should strive to welcome newcomers IMHO.

2021-02-18T11:52:39.374200Z

The clojure community is welcoming and does cater for beginners. The language however isn’t designed to appeal to beginners, it’s designed to first and foremost solve and avoid problems with other approaches. Beginners expectations don’t really align with good design as beginners essentially tend to want easy, not simple.

2021-02-18T11:57:48.374500Z

Beginners tend to want or expect things that are familiar etc.

2021-02-18T12:22:57.374800Z

Also I think it’s fair to say the core language itself prefers implementation simplicity in favour of features that primarily benefit the uninitiated. Some things like easier/friendlier error messages, could have in theory been baked in rather than reusing java’s exceptions etc… However doing so would probably have meant hiding or wrapping host platform features, which would be a lot of work, and would complicate the implementation, make interop more problematic, make work on clojure itself harder, and also be difficult to make as performant.

dominicm 2021-02-19T11:58:17.397800Z

Stu Halloway has some comments around the web riffing on the perception of "The most important measure for a language is how quickly a beginner can be productive". I think Rich talks about this in Design, Composition, Performance where he looks at different instruments. e.g. the Violin is not beginner friendly, but we don't need to send PRs to "fix" that, it's a tool that can be learned and you can make beautiful things in it once you do. Clojure's stance is that if there's a compromise between new-to-clojure devs and experienced-clojure-devs, the decision will always favour the latter.

👍 1
dharrigan 2021-02-18T05:13:16.352500Z

Thoughts/Opinions welcome!

jiriknesl 2021-02-18T06:38:58.353500Z

I think lazyness is necessary if you want a functional style in lots of cases.

mccraigmccraig 2021-02-18T06:41:27.355900Z

you are always going to be able to opt-in to lazy behaviour, since we have closures and macros, but seqs being lazy by default doesn't seem to be nearly as useful in practice as i once thought it might be

mccraigmccraig 2021-02-18T06:42:31.357100Z

tricks like (map + [1 2 3] (iterate inc 0)) are sure cute though

mccraigmccraig 2021-02-18T06:43:51.358100Z

or, this one i have used: (map vector (iterate inc 0) [:foo :bar :baz])

jiriknesl 2021-02-18T06:45:51.359200Z

I think, most of the issues are caused by mixing lazy and eager constructs without any visible difference. In comparison with that, transient and persistent data structures are easy to spot; modifications of state are easy to spot; core.async blocking operations are easy to spot.

mccraigmccraig 2021-02-18T06:50:27.361300Z

ah, you mean the loss of context on errors during lazy thunks... i do remember that being difficult to wrap my head around when learning clojure

thomas 2021-02-18T08:24:09.361700Z

mogge

djm 2021-02-18T08:32:42.361900Z

👋

dominicm 2021-02-18T09:34:34.362100Z

Morning

dominicm 2021-02-18T09:35:49.363300Z

I don't think laziness would be omitted, I've not heard that notion before. I think laziness wouldn't be the default for the sequence operations and instead transducers would feature more strongly in the core with laziness just being one possible consumption of that.

👍 1
alexlynham 2021-02-18T10:41:35.363800Z

i think @jiriknesl has it pretty much down - laziness in the lang is very desirable, the stumbling point is mixing lazy and eager. conceptually a lot easier to deal with when you get used to thinking in terms of instructions vs actions (or map vs reduce/fold etc)

2021-02-18T10:42:32.364Z

Morn'

2021-02-18T10:48:22.367500Z

@dharrigan I've had thoughts to that effect, but I'm beginning to rethink my position to something closer to what @jiriknesl suggested, though I've not considered it in terms of clearly separating them 😃... I do however find it useful to use eager constructs with a lazy context. @jiriknesl When you're thinking of visible difference, do you mean some way of more clearly marking the difference? Similar to ! to indicate side effects?

alexlynham 2021-02-18T10:52:14.367600Z

cough i have to admit that when you have types, it's def easier when the compiler goes NO NO STUPID PROGRAMMER THAT ONE IS LAZY which i have found useful with laziness in non-clj situations

👍 1
danm 2021-02-18T10:56:12.367800Z

Morning

2021-02-18T11:08:06.368200Z

Morning all!

2021-02-18T11:08:23.368600Z

Can I ask what web servers/frameworks people like to use?

dharrigan 2021-02-18T11:17:30.370Z

for my stuff, I'm quite happily using reitit + malli (for REST/routing/specs), honeysql (v1 and experimenting with v2), juxt clip (for lifecycle management), aero (for configuration) and a few other problem specific libraries (for kafka, redis)

danielneal 2021-02-18T11:23:36.370200Z

I think graphql with lacinia is pretty good

danielneal 2021-02-18T11:24:48.371800Z

Does anyone know of a tool for assessing clojure code complexity? I'm in a new codebase and I'm finding it really tough going, I think it would be great if I could somehow rank all the functions/macros etc in the codebase and give each one a complexity score, so we can target refactoring in the most useful places. Something like complexity of function = number of functions called x the rarity of each function. So that a function that calls a lot of infrequenctly used functions is high complexity (requires a lot of investment to understand) and a function that calls a few frequently used functions is low complexity.

dominicm 2021-02-20T10:21:32.002200Z

Oh nice, i'd been wondering about this. Always knew it would be possible, but wasn't sure if it existed.

2021-02-18T11:25:55.372200Z

I really like liberator, but agree that graphql with lacinia is pretty cool too

2021-02-18T11:39:15.372300Z

the most useful tool is probably your brain then

2021-02-18T11:39:34.372500Z

i.e. if you find it hard to read then that is the best metric to judge it on

2021-02-18T11:41:04.372700Z

also a lot of readability comes down to good naming, and I doubt you'll find an automated tool to test for name quality

Conor 2021-02-18T11:44:19.373100Z

You could give https://github.com/lokori/uncomplexor/blob/master/src/leiningen/uncomplexor.clj a go as a rough metric

Conor 2021-02-18T11:44:56.373400Z

There is also a Clojure plugin for Sonarqube that (among other things) can measure code coverage which might be helpful

danielneal 2021-02-18T11:47:13.373800Z

oh interesting, that complexor thing looks like a good starting point

alexlynham 2021-02-18T12:27:20.375Z

i liked liberator but ultimately the model they use is too like a pants reader monad for me to be super stoked about using it again

alexlynham 2021-02-18T12:27:55.375100Z

coeffecty/hook/implicit style stuff like that is just not my jam anymore

alexlynham 2021-02-18T12:28:20.375200Z

(although i've not used it a while, maybe the api has changed)

2021-02-18T12:33:41.376200Z

there's a couple that want to be run as lein templates - I've never really understood that - perhaps it's just me that has no idea what features they are going to need at the beginning of a project

2021-02-18T12:34:08.376800Z

also, if you're using tools.deps then what do you do? make a lein project and then manually convert it?

alexlynham 2021-02-18T13:11:01.377100Z

@mccraigmccraig trying to remember the codebase, are you using Eithers for error handling at all?

alexlynham 2021-02-18T13:13:53.377200Z

(as in, non fatal stuff, i guess, though you can also catch and still (Either.left <ex-info>) i guess)

mccraigmccraig 2021-02-18T13:21:11.378400Z

i'm currently exploring with a greenfield lib project @alex.lynham ... which i may or may not try and reverse into funcool/cats later, depending on how the answer to some questions pans out

mccraigmccraig 2021-02-18T13:23:56.380700Z

the impl i'm working on atm uses an Either approach, as part of a ReaderWriterExceptionTransformer - and you can see catch turns out to be the same as bind , but applies the function to the failure branch - https://github.com/mccraigmccraig/laters/blob/exploration/src/laters/control/rwexception.cljc#L144

mccraigmccraig 2021-02-18T13:51:31.381Z

doing exactly that in the bind impl, then providing catch and reject on the monad interface, so that they work seamlessly with more complex types - e.g. writer log is preserved through error handling

mccraigmccraig 2021-02-18T13:54:13.381200Z

for extra points i'm doing all the monads i'm interested in as monad-transformers, so then i have an inner-context which does auto-lifting of values between different monad types, which is nice in a dynlang where the compiler won't let me know that a particular fn or value needs lifting

alexlynham 2021-02-18T13:57:48.381400Z

right so it's kinda the same as a fold over the tagged union where it's only affecting the left branch? like, catching it

alexlynham 2021-02-18T13:58:14.381500Z

ah gotcha

alexlynham 2021-02-18T13:58:55.381700Z

i understand the words, not sure if i fully grok it

alexlynham 2021-02-18T13:59:11.381900Z

you mean impls provided to lift between the common monad types you're defining?

mccraigmccraig 2021-02-18T14:00:49.382100Z

yep, where feasible... e.g. i've got a synchronous ReaderWriterException which lifts easily to an async ReaderWriterPromise or i guess ReaderWriterTask in the terminology you were using a while back

mccraigmccraig 2021-02-18T14:01:46.382300Z

the idea being that i can take existing code which deals with simple types, just plain Promise or whatever, and mix it easily with new code which is ReaderWriterPromise

mccraigmccraig 2021-02-18T14:03:55.383800Z

perhaps ... catch threads the value inside the left branch into bindas if it were a right value

alexlynham 2021-02-18T16:40:18.384100Z

oh right

alexlynham 2021-02-18T16:40:33.384200Z

so catch is basically catch && return right

alexlynham 2021-02-18T16:40:41.384300Z

so left isn't triggered

alexlynham 2021-02-18T16:40:44.384400Z

gotcha

mccraigmccraig 2021-02-18T16:50:36.385500Z

here's an example... using handle rather than catch, but samesame... you can see you have access to the writer during handling, and the handle bifn is basically a bind fn - https://gist.github.com/mccraigmccraig/81ae4e4ff90334676589515d2c570430

mccraigmccraig 2021-02-18T16:51:31.386Z

(i'm not happy with the mlet syntax yet though... still too much noise)

mccraigmccraig 2021-02-18T16:55:25.386100Z

catch is just catch... it's up to you whether you return or reject (or throw if you want - that's equivalent to reject) - hence it's like bind - plain value in, monadic value out

alexlynham 2021-02-18T16:59:00.386300Z

right right

alexlynham 2021-02-18T16:59:43.386500Z

could you do it with a ddo style syntax?

alexlynham 2021-02-18T17:00:00.386600Z

such that

alexlynham 2021-02-18T17:01:02.386700Z

cos it seems like there are nested mlets

alexlynham 2021-02-18T17:01:30.386800Z

which makes me think it should be possible to compose the returned monads from functions with those bindings internally

alexlynham 2021-02-18T17:02:04.386900Z

cos every nested mlet could be reified into a separate function and then bound within a ddo right? same as haskell/typescript do

mccraigmccraig 2021-02-18T17:02:18.387Z

yeah, i think i can do better though... and infer the context from the mvs in most cases

mccraigmccraig 2021-02-18T17:02:49.387100Z

it's the same sig as catchError in haskell - https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Except.html

alexlynham 2021-02-18T17:02:56.387300Z

mvs?

mccraigmccraig 2021-02-18T17:03:56.387500Z

monadic-values

mccraigmccraig 2021-02-18T17:08:42.390500Z

the fns which don't take a monadic-value are the tough ones (like return )... haskell can infer their type, i think from the result hole... cats uses a combination of dynvar and protocols... i'm currently using a lexically scoped binding, but it's lead to macros where i don't really want macros

alexlynham 2021-02-18T17:09:11.390600Z

could you metadata tag?

alexlynham 2021-02-18T17:09:34.390700Z

i mean all ADTs are sort of waves hands variants under the hood

alexlynham 2021-02-18T17:09:39.390800Z

sort of

alexlynham 2021-02-18T17:09:51.390900Z

in the sense of it's possible to make that explicit

mccraigmccraig 2021-02-18T17:15:40.392800Z

yeah, i'm keeping a tag around once something is wrapped in a context, it's the very first step that's the issue

alexlynham 2021-02-18T17:18:24.392900Z

the very first step?

alexlynham 2021-02-18T17:18:32.393Z

establishing the context you mean?

alexlynham 2021-02-18T17:18:54.393100Z

so writing monad constructors? that's always p verbose i guess

mccraigmccraig 2021-02-18T17:25:14.394800Z

i have confidence it will all fall out soon enough... everything has so far

mccraigmccraig 2021-02-18T18:23:29.394900Z

much better... i'm pretty happy with this: https://gist.github.com/mccraigmccraig/07a6ef639eff0d5c6957c5fe60b12b92

dharrigan 2021-02-18T21:05:18.395600Z

Been watching the Mars landing live

dharrigan 2021-02-18T21:05:21.395800Z

very exciting stuff