Live data from Hacker News

Six Years of Professional Clojure

engineering.nanit.com

181–190 of 254 posts

Re: Six Years of Professional Clojure

#181
post #3

I found one of the perceived weaknesses of Clojure (in this article), it being dynamically typed, is a tradeoff rather than a pure negative. But it applies that tradeoff differently than dynamic languages I know otherwise and that difference is qualitative: It enables a truly interactive way of development that keeps your mind in the code, while it is running. This is why people get addicted to Lisp, Smalltalk and si…

In 2021, I find it hard to justify using a dynamically typed language for any project that exceeds a few hundreds of lines. It's not a trade off, it's a net loss. The current crop of statically typed languages (from the oldest ones, e.g. C#, to the more recent ones, e.g. Kotlin and Rust) is basically doing everything that dynamically typed languages used to have a monopoly on, but on top of that, they offer performan…

> automatic refactorings (pretty much impossible to achieve on dynamically typed languages without human supervision)

...are we talking about the thing pioneered by Smalltalk's Refactoring Browser?

Re: Six Years of Professional Clojure

#182

Earlier quoted context omitted.

I think you are mistaken. Mocking and DI frameworks are two unrelated concepts. There is nothing in Java that forces you to use a DI framework, e.g., Spring if you want to use mocks during testing.

In theory, I agree, but I don't think that holds terribly true in practice. One of the ideas behind IoC frameworks (which build on top of DI) is that you could swap out implementation classes. For a great deal of software (and especially in cloud-hosted, SaaS style microservice architecture) the test stubs are the only other implementations that ever get injected. Most code bases could ditch IoC if Java provided a la…

Java has a mechanism, just pass alternate implemenations in constructors. If you must, a setter method. For most code you don't need to bring in the overhead of Spring, and @Autowired isn't really more convenient typing wise. Plus your unit tests become trivial, they're just POJOs with @Test annotations.

Spring is great when you need that dynamic control at runtime (especially when code dependencies are separated by modules) but you're just aping what good dynamic languages like Clojure or Common Lisp give you for free. But I can't complain too much, developing modern Java with its popular frameworks and with JRebel is getting closer to the Lisp experience every year, I'd rather have that than for Java to remain stagnate like in its 1.6/1.7 days.

Re: Six Years of Professional Clojure

#183

Earlier quoted context omitted.

I think you are mistaken. Mocking and DI frameworks are two unrelated concepts. There is nothing in Java that forces you to use a DI framework, e.g., Spring if you want to use mocks during testing.

Let's say I have a class called User and in it a method that says the current time. So User#say_current_time which simply accesses the Date class (it takes no arguments). Can you show me how you would mock the current time of that method in Java? It's one line of Ruby/Javascript code to do that.

Without using a mock framework, assuming User#say_current_time isn't a private or static method then:

    final Date testDate = someFixedDate;
    User testUser = new User() {
        @Override
        Date say_current_time() {
            return testDate;
        }
    };
If it is private and/or static, you can get around it without having to change the code, but if you own the code, you should just do that... Often the change will be as simple as replacing some method's raw usage of Date.now() with a local say_curent_time() method that uses it or some injected dependency just so you can mock Date.now() without hassle.

But your point further down that in Java you have to think about your code structure more to accommodate tests is valid. I think it's easy to drink the kool-aid and start believing that many code structuring styles that enable easier testing in Java are actually very often just better styles regardless of language, but you're not going to really see the point if you do nothing but Ruby/JS where you can get away with not doing such things for longer. Mostly it has to do with dynamic languages offering looser and later and dynamic binding than static languages (which also frequently makes them easier to refactor even if you don't have automated tools). One big exception is if your language supports multiple dispatch, a lot of super ugly Java-isms go away and you shouldn't emulate them. The book Working Effectively with Legacy Code is a good reference for what works well in Java and C++ (and similar situations in other languages), it's mostly about techniques for breaking dependencies.

Re: Six Years of Professional Clojure

#184
Being able to keep track of what data was where is the initial bump I had as well when learning Clojure. Unlike the author, I personally got used to it, and generally don't struggle with it anymore, but part of that is learning good habits on your code base where you make judicious use of names, doc-string, destructuring and have a well defined data model using records or Spec or Schema, etc.

The other one is just getting good at the REPL and inspecting the implementation for functions to quickly see what keys and all they make use of.

Something the article didn't really cover either is that it's not really the lack of static type checking that's the real culprit, its the data-oriented style of programming that is. If you modeled your data with generic data-structures even in Haskell, Java, C# or any other statically typed language, you'd have the same issue.

If Clojure used abstract data-types (ADTs) like is often the case in statically typed languages, things would already be simpler.

    (defrecord Name [first middle last])

    (defn greet
      [{:keys [first middle last]
        :as name}]
      (assert (instance? Name name))
      (println "Hello" first middle last))

    (greet (->Name "John" "Bobby" "Doe"))
This is how other languages work, all "entities" are created as ADTs, it has pros/cons off course, which is why Clojure tend to favour the data-oriented approach where you'd just do:

    (defn greet
      [{:keys [first middle last] 
        :as name}]
      (println "Hello" first middle last))
But as you see, this makes it harder to know what a Name is and what's possibly available on it.

Re: Six Years of Professional Clojure

#185

Earlier quoted context omitted.

Not even. It was opening it, looking, realizing it would take a couple weeks, and going back to F#. I did this a couple times before fully giving up. IIRC/IIUC, Clojure's async support is closer to Go's (I've never used go), in the form of explicit channels. Though you can wrap that in a monad pretty easily, which I did for fun one day ( https://gist.github.com/daxfohl/5ca4da331901596ae376 ). But neither option was e…

Sounds more like you ran into a conflict of mental model and language feature, not necessarily that the language couldn’t achieve your goal simply.

Yeah, quite possible. I haven't worked on the project in ~six years and lost all context, but I'd revisit it and see if perhaps there was a simple solution if any of it was still current.

Re: Six Years of Professional Clojure

#186
post #3

I found one of the perceived weaknesses of Clojure (in this article), it being dynamically typed, is a tradeoff rather than a pure negative. But it applies that tradeoff differently than dynamic languages I know otherwise and that difference is qualitative: It enables a truly interactive way of development that keeps your mind in the code, while it is running. This is why people get addicted to Lisp, Smalltalk and si…

Maybe I've just never given it a chance, but I've never understood the appeal of being able to modify code in-memory while it's running. I like a REPL for testing things out, or for doing quick one-off computations, but that's it. I would never want to, say, redefine a function in memory "while the code is running". Not just because of ergonomics, but because if I decide to keep that change, I now have to track down…

You can get an approximate taste of what it's like in plain old Java + JRebel, it's seriously about the same as trying to do it with Python + something like Flask. Start up a big application server in debug mode with Eclipse/IntelliJ, be annoyed that it takes 2+ minutes to start after everything's compiled. Now you want to work on a story, or some bug. You can make changes, save, it recompiles just that file (incremental compilation is a godsend in itself), and hotswaps it into the running application memory. No need to restart anything (with JRebel, for most common kinds of changes; without JRebel, only for some changes). It's particularly useful when you're debugging, you find the problem, change the code, and re-execute immediately to verify to yourself it's fixed.

You also can get an experience like PHP, where you just have to change and save some files, and your subsequent requests will use the new code. This is so much better than shutting down everything and restarting and is a large part of why CGI workflows dominated the web.

Common Lisp takes these experiences and dials them to 11, the whole language is built to reinforce the development style of dynamic changes, rather than an after-thought that requires a huge IDE+proprietary java agent. It's still best to use some sort of editor or IDE, and then you don't have any worry about source de-syncs -- frequently you'll make multiple changes across multiple files and then just reload the whole module and any files that changed with one function call, which you might bind to an editor shortcut, but crucially like debugging is not centrally a feature of the editor but the language; the language's plain REPL by itself is just a lot more supportive of interactive development than Python/JS/Ruby's. Clojure, and I personally think even Java with the appropriate tools, are between Python and CL for niceness of interactive development, but Clojure tends to be better than the Java IDE experience because of its other focus on immutability.

Re: Six Years of Professional Clojure

#187
post #97

Earlier quoted context omitted.

Yeah, I had a fairly large (about a year of solo dev work) app that I maintained both Clojure and F# ports of, doing a compare and contrast of the various language strengths. One day I refactored the F# to be async, a change that affected like half the codebase, but was completed pretty mechanically via changing the core lines, then following the red squigglies until everything compiled again, and it basically worked…

Hey, so my my career path has been C# (many years) -> F# (couple years) -> Clojure (3 months). I understand multithreading primarily through the lens of async/await, and have been having trouble fully grokking the Clojure's multithreading. One of the commandments of async/await is don't block: https://blog.stephencleary.com/2012/07/dont-block-on-async-c... Which is why the async monad tends to infect everything. Cloj…

Multi-threaded code is normally not implemented in an async style, but instead is done where each thread of execution is synchronous.

Async style comes into play generally for languages that lack real threads, or as a way to manage callbacks (even if single threaded), or in order to wait for blocking IO without the need for a real thread.

So ya, it's idiomatic to use blocking to coordinate between different threads in Clojure, same as Java.

Java decided to work on making stackful coroutines instead of stackless like C#. That requires a lot more work, but should be coming eventually to Java. At that point, your "blocking" code in Clojure will no longer block a real thread, but a lightweight fiber instead. But patience is needed for it.

In the meantime, if you're dealing with non-blocking IO that operates with callback semantics or other callback style code, what you can do in Clojure to make working with that easier is use one of:

> core.async - https://github.com/clojure/core.async

> Promesa - https://github.com/funcool/promesa

> Missionary - https://github.com/leonoel/missionary

> Missionary's lower level coroutine lib - https://github.com/leonoel/cloroutine/blob/master/doc/02-asy...

Re: Six Years of Professional Clojure

#188

Earlier quoted context omitted.

Your third point about having to encode everything isn’t quite true. Your example is just brittle in that it doesn’t allow additional values to show up causing it to break when they do. That’s not a feature of static type systems but how you wrote the code. This blog post[1] has a good explanation about it, if you can forgive the occasional snarkyness that the author employs. In a dynamic system you’re still encoding…

I've seen this article and I applaud it for addressing the issue thoroughly but I still am not convinced that static typing as we know it is as flexible and generic as dynamic typing. Let's go at this from an other angle, with a thought experiment. I hope you won't find it sarcastic or patronizing, just trying to draw an analogy here. So, in statically typed languages, it is not idiomatic to pass around heterogeneous…

It is absolutely possible to have the same type for values that have the same shape.

You can have a `Map k v` that is a record that dynamic languages have that they call object/map.(make k/v Object or Dynamic if you want)

You don't need to create a new type with precise information if you just want that(no you don't need to instantiate type params everywhere). There is definitely limitations in type-systems (requiring advanced acrobatics) but most programs don't run into them and HM type system (https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_sy...) has stood the test of time.

For a great introduction on the idea of a type system, see: https://www.youtube.com/watch?v=brE_dyedGm0 .

Re: Six Years of Professional Clojure

#189

Earlier quoted context omitted.

Not who you are responding to, but the common idea that static types are all win and no cost has become very popular these days, but isn't true, it's just that the benefits of static typing are immediately apparent and obvious, but their costs are more diffuse and less obvious. I thought this was a pretty good write up on the subject that gets at a few of the benefits https://lispcast.com/clojure-and-types/ Just to n…

Your third point about having to encode everything isn’t quite true. Your example is just brittle in that it doesn’t allow additional values to show up causing it to break when they do. That’s not a feature of static type systems but how you wrote the code. This blog post[1] has a good explanation about it, if you can forgive the occasional snarkyness that the author employs. In a dynamic system you’re still encoding…

I think many peoples' experience is that most real world data models aren't as perfect as making up toy examples in blog posts. Requirements and individuals change over time. You can make an argument that in a perfect world with infinite time and money that static typing may be better because you can always model things precisely, but whether you can do that practically over longer periods of time should be a debatable question.

Re: Six Years of Professional Clojure

#190
post #181

Earlier quoted context omitted.

In 2021, I find it hard to justify using a dynamically typed language for any project that exceeds a few hundreds of lines. It's not a trade off, it's a net loss. The current crop of statically typed languages (from the oldest ones, e.g. C#, to the more recent ones, e.g. Kotlin and Rust) is basically doing everything that dynamically typed languages used to have a monopoly on, but on top of that, they offer performan…

> automatic refactorings (pretty much impossible to achieve on dynamically typed languages without human supervision) ...are we talking about the thing pioneered by Smalltalk's Refactoring Browser?

My question is how does that work in a dynamically typed language? In static typed language we can know scope & type of a variable and we can't change much in runtime.
Post reply on HN