Live data from Hacker News

Six Years of Professional Clojure

engineering.nanit.com

121–130 of 254 posts

Re: Six Years of Professional Clojure

#121

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…

> 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. Only if you are skimping on tests. There's a tradeoff here - "dynamically typed" languages generally are way easier to write tests for. The expectation is that you will have plenty of them. Given that most language's type systems are horrible (Java and C#…

Here's what I've noticed with my tests and dynamic languages. I'll get type errors that static typing would have caught. However those errors occur in places I was missing testing of actual functionality. Had I had the functionality tests, then the type error would have been picked up by my tests. And had I just had static typing, the type system would not have been enough to prove the code actually works, so I would have needed tests anyways.

Point being, I don't really buy that a static type system saves me any time writing and maintaining tests, because type systems are totally unable to express algorithms. And with a working test suite (which you will need regardless of static vs dynamic) large refactors become just as mechanical in dynamic languages as they are in static languages.

Re: Six Years of Professional Clojure

#122
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…

[deleted]

Re: Six Years of Professional Clojure

#123

Earlier quoted context omitted.

It's your opinion though, there's nothing scientific about what you're saying. Take mocking for example, in Ruby/Rails it's a breeze. In Java you need to invent a dependency injection framework (Spring) to do it.

The fact that there are such libraries in existence means that there is no pain associated to this particular activity. Not only do you get great mocking frameworks, they are actually very robust and benefit from static types. Mocking dynamically typed languages is monkey patching, something that the industry has been moving away from for more than a decade. And for good reasons.

> The fact that there are such libraries in existence means that there is no pain associated to this particular activity

I can say the same about Rails + RSpec. It exists therefore it's good.

> Mocking dynamically typed languages is monkey patching, something that the industry has been moving away

That's a reach. There are millions of javascript/python/php/ruby/elixir devs that don't use types or annotations. They mock. "The industry" isn't one cohesive thing.

Re: Six Years of Professional Clojure

#124

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.

    User mock = mock(User.java)
    when(mock.say_current_time()).thenReturn(someDate)

Re: Six Years of Professional Clojure

#125
post #39

I started working professionally with Clojure earlier this year and this article rings true. I think the article leaves out a fourth downside to running on the JVM: cryptic stack traces. Clojure will often throw Java errors when you do something wrong in Clojure. It's a bit of a pain to reason about what part of your Clojure code this Java error relates to, especially when just starting out.

To be fair, this is not unique to Clojure. You need to deal with stack traces no matter what as long as you're using any programming language that targets the JVM (even statically type-checked languages like Scala). There are some great articles [1][2] that discuss various simple techniques helpful for debugging and dealing with stack traces. [1] https://eli.thegreenplace.net/2017/notes-on-debugging-clojur... [2] htt…

I've never really had a problem with stack traces in Scala. Every once in a while you hit a cryptic one that's buried in Java library code, but for the most part they're runtime errors that are due to incompletely tested code or some kind of handled error with a very specific message.

Re: Six Years of Professional Clojure

#126

Earlier quoted context omitted.

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.

User mock = mock(User.java) when(mock.say_current_time()).thenReturn(someDate)

OK. first I could be ignorant about Java since I haven't touched it in more than a decade. Which library is doing that? And also what is mock(User.java) returning - is it an actual User instance or a stub? I want a real User instance (nothing mocked in it) with just the one method mocked.

And again if this is possible I will admit ignorance and tip my hat at the Java guys.

Re: Six Years of Professional Clojure

#127
post #95

Earlier quoted context omitted.

I must respectfully disagree with the points you've brought up.

Can you elaborate why? To be honest, I don't have experience with large-scale Clojure codebases, but I have my fair share working on fairly hefty Python and Perl projects, and I tend to think that the parent commenter is mostly right. What makes you think they are incorrect?

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 name some of the costs of static types briefly:

* they are very blunt -- they will forbid many perfectly valid programs just on the basis that you haven't fit your program into the type system's view of how to encode invariants. So in a static typing language you are always to greater or lesser extent modifying your code away from how you could have naturally expressed the functionality towards helping the compiler understand it.

* Sometimes this is not such a big change from how you'd otherwise write, but other times the challenge of writing some code could be virtually completely in the problem of how to express your invariants within the type system, and it becomes an obsession/game. I've seen this run rampant in the Scala world where the complexity of code reaches the level of satire.

* Everything you encode via static types is something that you would actually have to change your code to allow it to change. Maybe this seems obvious, but it has big implications against how coupled and fragile your code is. Consider in Scala you're parsing a document into a static type like.

    case class Record(
      id: Long,
      name: String,
      createTs: Instant,
      tags: Tags,
    } 
    
    case class Tags(
      maker: Option[String],
      category: Option[Category],
      source: Option[Source],
    )
//...

In this example, what happens if there are new fields on Records or Tags? Our program can't "pass through" this data from one end to an other without knowing about it and updating the code to reflect these changes. What if there's a new Tag added? That's a refactor+redeploy. What if the Category tag adds a new field? refactor+redeply. In a language as open and flexible as Clojure, this information can pass through your application without issue. Clojure programs are able to be less fragile and coupled because of this.

* Using dynamic maps to represent data allows you to program generically and allows for better code reuse, again in a less coupled way than you would be able to easily achieve in static types. Consider for instance how you would do something like `(select-keys record [:id :create-ts])` in Scala. You'd have to hand-code that implementation for every kind of object you want to use it on. What about something like updating all updatable fields of an object? Again you'll have to hardcode that for all objects in scala like

    case class UpdatableRecordFields(name: Option[String], tags: Option[Tags]) 
    def update(r: Record, updatableFields: UpdatableRecordFields) = {
      var result = r
      updatableFields.name.foreach(r = r.copy(name = _))
      updatableFields.tags.foreach(r = r.copy(tags = _))
      result
    }
all this is specific code and not reusable! In clojure, you can solve this for once and for all!

    (defn update [{:keys [prev-obj new-obj updatable-fields}]
      (merge obj (select-keys new-fields updatable-fields)))
    
    (update 
      {:prev-obj {:id 1 :name "ross" :createTs (now) :tags {:category "Toys"}} 
       :new-obj {:name "rachel"} 
       :updatable-fields [:name :tags]})
      => {:id 1 :name "rachel" :createTs (now) :tags {:category "Toys"}}  

I think Rich Hickey made this point really well in this funny rant https://youtu.be/aSEQfqNYNAc.

Anyways I could go on but have to get back to work, cheers!

Re: Six Years of Professional Clojure

#128

> An incoming HTTP request? it is a plain Clojure dictionary. I learned to code in Python. Loved it. Dynamically typed dicts up the wazoo! Then I learned why I prefer actual types. Because then when I read code, I don't have to read the code that populates the dicts to understand what fields exist.

This is one of those self-inflicted Clojure problems. In Common Lisp you might use an alist or a plist for small things, but you'd definitely reach for CLOS classes for things that had relationships to other things and things that had greater complexity. IIRC, the preference for complecting things via maps, and then beating back the hordes of problems with that via clojure.spec.alpha (alpha2?) is a Hickey preference.…

No source to back this up, but my guess is that Clojure was driven by the need to interopt with Java so is to not get kicked out of production. This meant absorbing the Java object model. Shipping a language with both Java objects and CLOS and making them both play nice together sounds like a nightmare.

Re: Six Years of Professional Clojure

#129
post #58
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…

Except, of course, that specs are only tested correct, not proven correct like types would be. Types (in a reasonable static type system, not, say, C) are never wrong. In addition, specs do not compose, do they ? If you call a function g in a function f, there is no automatic check that their specs align.

> Types (in a reasonable static type system, not, say, C) are never wrong.

Oh man. This is the fundamental disagreement. Sure, you can have a type system that is never wrong in its own little world. But, that's not the problem. A lot of us are making a living mapping real world problems into software solutions. If that mapping is messed up (and it always is to some degree) then the formal correctness of the type system doesn't matter at all. It's like you got the wrong answer really, really right.

Re: Six Years of Professional Clojure

#130

Earlier quoted context omitted.

User mock = mock(User.java) when(mock.say_current_time()).thenReturn(someDate)

OK. first I could be ignorant about Java since I haven't touched it in more than a decade. Which library is doing that? And also what is mock(User.java) returning - is it an actual User instance or a stub? I want a real User instance (nothing mocked in it) with just the one method mocked. And again if this is possible I will admit ignorance and tip my hat at the Java guys.

It's Mockito [1], which has been a standard for a while. There are other libraries and they use different strategies to provide this kind of functionalities (dynamic proxies, bytecode weaving, annotation processing, etc...).

[1] https://site.mockito.org/

Post reply on HN