HN Theater @HNTheaterMonth

The best talks and videos of Hacker News.

Hacker News Comments on
Rich Hickey rants about HttpServletRequest in 1080p: Death By Specificity from Clojure Made Simple

Fred Overflow · Youtube · 12 HN points · 6 HN comments
HN Theater has aggregated all Hacker News stories and comments that mention Fred Overflow's video "Rich Hickey rants about HttpServletRequest in 1080p: Death By Specificity from Clojure Made Simple".
Youtube Summary
excerpt from https://www.youtube.com/watch?v=VSdnJDO-xdg with new slides
HN Theater Rankings

Hacker News Stories and Comments

All the comments and stories posted to Hacker News that reference this video.
> I found Rust syntax to be dense, heavy, and difficult to read

Reminds me of this section of Rich Hickey talk: https://www.youtube.com/watch?v=aSEQfqNYNAc

voorwerpjes
There are similar issues in Rust to the one Hickey talks about in Java, in terms of cognitive overload and difficulty in a human parsing the program. However, I've found rust largely avoids issues with a bunch of different classes and with their own specific interfaces with a bunch of getters and setters in the HTTP servlet example because of Trait interface reuse.
> there was no type-checking (...) intellisense/autocomplete popups for the methods.

First of all, yes there is. Maybe your editor didn't support it, but e.g. IntelliJ IDEA with the Cursive plugin does.

Secondly: https://www.youtube.com/watch?v=aSEQfqNYNAc

Sep 01, 2021 · 1 points, 0 comments · submitted by tosh
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!

ud_visa
Let me address your criticism from Scala's point of view

> they are very blunt

I'm more blunt than the complier usually. I really want 'clever' programs to be rejected. In rare situations when I'm sure I know something the complier doesn't, there are escape hatches like type casting or @ignoreVariace annotation.

> the problem of how to express your invariants within the type system

The decision of where to stop to encode invariants using the type system totally depends on a programmer. Experience matters here.

> Our program can't "pass through" this data from one end to an other

It's a valid point, but can be addressed by passing data as tuple (parsedData, originalData).

> What if there's a new Tag added? What if the Category tag adds a new field?

If it doesn't require changes in your code, you've modelled your domain wrong - tags should be just a Map[String, String]. If it does, you have to refactor+redeploy anyway.

> What about something like updating all updatable fields of an object

I'm not sure what exactly you meant here, but if you want to transform object in a boilerplate-free way, macroses are the answer. There is even a library for this exact purpose: https://scalalandio.github.io/chimney/! C# and Java have to resort to reflection, unfortunately.

throwaway_fjmr
> 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.

Or this can wreak havoc :) Nothing stops you from writing Map<Object, Object> or Map[Any, Any], right?

uDontKnowMe
That's true! But now we'll get into what is possible vs what is idiomatic, common, and supported by the language/stdlib/tooling/libraries/community. If I remember correctly, Rich Hickey did actually do some development for the US census, programming sort of in a Clojure way but in C#, before creating Clojure. But it just looked so alien and was so high-friction that he ended up just creating Clojure. As the article I linked to points out, "at some point, you're just re-implementing Clojure". That being said, it's definitely possible, I just have almost never seen anyone program like that in Java/Scala.
codingkoi
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 the type of the data, just less explicitly than you would in a static system and without all the aid the compiler would give you to make sure you do it right.

[1]: https://lexi-lambda.github.io/blog/2020/01/19/no-dynamic-typ...

jhhh
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.
uDontKnowMe
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 dynamic maps, at least in application code, like it is in Ruby/Clojure/etc. But one analogy we can draw which could drive some intuition for static typing enthusiasts is to forget about objects and consider lists. It is perfectly familiar to Scala/Java/C# programmers to pass around Lists, even though they're highly dynamic. So now think about what programming would be like if we didn't have dynamic lists, and instead whenever you wanted to build a collection, you had to go through the same rigamarole that you have to when defining a new User/Record/Tags object.

So instead of being able to use fully general `List` objects, when you want to create a list, that will be its own custom type. So instead of

  val list = List(1,2,3,4)
you'll have to do:

    case class List4(_0: Int, _1: Int, _2: Int, _3: Int)
    val list = List4(1,2,3,4)
This represents what we're trying to do much more accurately and type-safely than with dynamic Lists, but what is the cost? We can't append to the list, we can't `.map(...)` the list, we can't take the sum of the list. Well, actually we can!

    case class List5(_0: Int, _1: Int, _2: Int, _3: Int, _4)
    def append(list4: List4, elem: Int): List5 = List5(list4._0, list4._1, list4._2, list4._3, elem)
    def map(list4: List4, f: Int => Int): List4 = List4(f(list4._0), f(list4._1), f(list4._2), f(list4._3))
    def sum(list4: List4): Int = list4._0 + list4._1 + list4._2 + list4._3
So what's the problem? I've shown that the statically defined list is can handle the cases that I initially thought were missing. In fact, for any such operation you are missing from the dynamic list implementation, I can come up with a static version which will be much more type safe and more explicit on what it expects and what it returns.

I think it's obvious what is missing, it's that all this code is way too specific, you can't reuse any code from List4 in List5, and just a whole host of other problems. Well, this is pretty much exactly the same kinds of problems that you run into with static typing when you're applying it to domain objects like User/Record/Car. It's just that we're very used to these limitations, so it never really occurs to us what kind of cost we're paying for the guarantees we're getting.

That's not to say dynamic typing is right and static typing is wrong, but I do think that there really are significant costs to static typing and people don't think about it.

playing_colours
I believe your comments provided a good insight into your approach to programming. I may be wrong in my understanding, but let me elaborate.

You expect your programming language to be a continuation of your thoughts, it should be flexible and ductile to your improvisations. You see static typing as a cumbersome restricting bureaucracy you have to obey to.

Whereas I see type system like a tool that helps to structure my thoughts, define the rules and interfaces between construction blocks of my program. It is a scaffolding for a growing body of code. I found that in many cases, well defined data structures and declarations of functions are enough to clearly describe how some piece of code is meant to work.

It seems we developed different preferred ways of writing code, maybe, influenced by our primary languages, features of character, type of software we create. I used Scala for several years, but recently I regularly use Python. Shaping my code with dataclasses and empty functions is my preferred way to begin.

xfer
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 .

codingkoi
I’m not sure I follow your analogy. I think the dynamism of a list is separate from the type system. I can say I have a list of integers but that doesn’t limit its size.

I can think of instances where that might be useful and I think there’s even work being done in that direction in things like Idris that I really know very little about.

There are trade offs in everything. I’m definitely a fan of dynamic type systems especially things like Lisp and Smalltalk where I can interact with the running system as I go, and not having to specify types up front helps with that. Type inference will get you close to that in a more static system, but it can only do so much.

The value I see in static type systems comes from being able to rely on the tooling to help me reason about what I’m trying to build, especially as it gets larger. I think of this as being something like what Doug Englebert was pointing at when he talked about augmented intelligence.

I use Python at work and while there are tools that can do some pretty decent static analysis of it, I find myself longing for something like Rust more and more.

Another example I would point to beyond the blog post I previously mentioned is Rust’s serde library. It totally allows you to round trip data while only specifiying the parts you care about. I don’t think static type systems are as static as most like to think. It’s more about knowns and unknowns and being explicit about them.

didibus
It's important to note that this article talks about something that is missing from most statically typed languages.

It's best to refrain from debating static VS dynamic as generic stereotype and catch all.

You need to look at Clojure vs X, where if X is Haskell, Java, Kotlin and C#, what the article talks about doesn't apply and Clojure has the edge. If it's OCaml or F# than they in some scenarios don't suffer from that issue like the others and equal Clojure. But then there are other aspects to consider if your were to do a full comparison.

In that way, one needs to understand the full scope of Clojure's trade offs as a whole. It was not made "dynamic" for fun.

Overall, most programming languages are quite well balanced with regards to each other and their trade-offs. What matters more is which one fits your playing style best.

Rich Hickey "thinks in lisp". For example, he talks about his "maps" data structure (aka association lists). See https://www.youtube.com/watch?v=aSEQfqNYNAc

Since data and programs are the same thing in lisp you can fluidly shift from one to the other, like I did with "knowledge" vs "rules". They both have the same representation (defstructs) but they are also code and have execution semantics. Clojure allows you to do this with map objects.

The fact that you can use the whole language on everything at any time and that everything at any time is also "the language" is how you "think in lisp". Nobody thinks of Python programs as "data" or data as Python programs.

Watch anything Rich Hickey talks about. He really "gets it".

Apr 12, 2021 · 7 points, 0 comments · submitted by Scarbutt
Dec 11, 2020 · simongray on The Future of Clojure
The same amount of static analysis as Python, Ruby, or any other language that doesn't have static type checking. Some people need something to happen when they press dot, though: https://www.youtube.com/watch?v=aSEQfqNYNAc
omginternets
Gotcha, that's kind of what I figured.

Frankly, VSCode is reasonably good at giving you something for Python, and the relative lack of static analysis is offset by other qualities of the language, so this feels like a pretty weak argument.

mto
As I've been mostly using Python for the last 10 years I forgot how nice this dot thing can be ;). True that vscode and pycharm can give you something but I found that it's very often crap.

I am writing a little bit of C# atm and astonished how you can just dot tab through your work (without ever having read a tutorial or book on C#) and all those other contextual information you get with "full" visual studio. Somehow feels like just letting you guide through the work :).

On the other hand, the students I teach really struggle when they don't have all their IDE tricks. They learn almost exclusively C# at their institution in the first year(s) while my experience is mostly playing with Unity ;).

> I have an object... what do I do with it? So I hit the '.' on my keyboard and read the list of things that the object can do.

As a counterpoint (relevant Rich Hickey rant): https://www.youtube.com/watch?v=aSEQfqNYNAc

onemoresoop
I have a counterpoint. OOP made simple things way too complicated and that resulted in more jobs in software development. Its the sad truth. Or the happy one, depending on which side of the fence one is.
oftenwrong
The poor design of that class aside, I would rather have the specific version rather than the unspecified version. If I have a HttpServletRequest somewhere in my code, I have a good idea of its structure just by knowing its type. The same cannot be said of a blob of maps and lists.
simongray
> If I have a HttpServletRequest somewhere in my code, I have a good idea of its structure just by knowing its type. The same cannot be said of a blob of maps and lists.

Non-trivial data structures are always conformed to some specification, doesn't matter if its Clojure or an OOP-language. If you receive an HTTP request over the wire you will have to check it. Once you conform it to the specification you know exactly how it looks and there's no need to jump through hoops to access the information.

kqr
But with proper static typing you don't have to check it because you already know it's correct since it's construction. It relieves a lot of mental burden for me to not even have to consider the case that it might be incorrect, as long as I'm dealing with structures internal to my program.

I have a lot of respect for Rich Hickey and I feel like he Understands something that goes straight over my head, but his appreciation for generic, shapeless maps and lists is something I just don't get.

simongray
> But with proper static typing you don't have to check it because you already know it's correct since it's construction.

Its construction is dependent on it conforming to a specific form. In this case, your statically typed data comes from a parsed string that is then conformed to a specification to ensure that is has a certain shape. In Clojure you would just do the conforming to specification part and keep the data generic.

I think the appreciation for generic data has to do with the fact the fact that it makes Clojure code very generic and facilitates a lot of functional composition.

Apr 22, 2020 · 4 points, 0 comments · submitted by oftenwrong
HN Theater is an independent project and is not operated by Y Combinator or any of the video hosting platforms linked to on this site.
~ yaj@
;laksdfhjdhksalkfj more things
yahnd.com ~ Privacy Policy ~
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.