Live data from Hacker News

My thoughts after using Clojure for about a month

acdw.net

191–200 of 204 posts

Re: My thoughts after using Clojure for about a month

#191
post #118

> "I do wish there were an easier way to move in the ]}]})))}-ness of block ends though." If he means navigating the AST, there is Parinfer: https://shaunlebron.github.io/parinfer/ Paredit / Parinfer ruined other languages for me. It lets you navigate up/down/in/out of the Clojure AST with keyboard commands and mutate those expressions, e.g. "Split" will split open the current data structure you're in: `(a| b)` =Spli…

Navigating and manipulating code as a tree, directly in your editor. There is nothing like it.

Going back to code as bunch of carefully arranged ascii chars feels like a regression.

Re: My thoughts after using Clojure for about a month

#192
post #38

Once you learn Clojure's syntax and semantics, you're no longer bound to the JVM. There's ClojureScript (JS), ClojureCLR, ClojureDart, jank (C++), Basilisp (Python), babashka (SCI), and many others. This means that, if you don't know Java or don't like the JVM, you can likely use Clojure wherever you already feel most comfortable. For the most part, any Clojure code which doesn't use host interop will work on all dia…

As someone who loves Clojure, I wonder about the real portability across host languages. Do you have experience with any of these other dialects? (beyond the obvious CLJS & Babashka?)

I am learning Clojure this week, and my test project is a calculator / unit convertor [1]. I wanted it to run in the CLI and on the web, so it targets several hosted platforms: Babashka / JVM / ClojureScript. It's a single code base written in cross platform .cljc files. I already have about 250 tests written for the abstract calculator API, run as a test matrix across platforms, so the project is already in a good place for testing a new runtime.

I just learned about basilisp from the parent comment, so I asked Claude to add Python support to the same .cljc files I have, and we finished the port in about 30mins, and then fixed Python specific test cases for another 30 mins, but now all of the existing tests are passing. That's impressive in several ways.

Portability is achieved by testing. You have to put the platforms you want to support into your test harness, and the earlier the better. A calculator is purely functional, so this is a fairly straight forward port and really easy to test for. I'm not sure about larger projects, but it seems like there is something seriously right about Clojure's design that makes this easy to do.

[1] https://github.com/EnigmaCurry/calc

Re: My thoughts after using Clojure for about a month

#193
Why are iLemmings comments dead / flagged [0] [1]?

Can't even reply inline and have to reply in the main thread.

I found them informative.

Lots of Clojure haters here I guess?

[0] https://news.ycombinator.com/item?id=48375393#48387700

[1] https://news.ycombinator.com/item?id=48375393#48387382

Re: My thoughts after using Clojure for about a month

#194
post #166

Earlier quoted context omitted.

> babashka (SCI) Correct me if I'm wrong, but isn't babashka's "host"... um.. "native", for lack of a better word? It's compiled with Graal VM native, no? Yes, there is SCI (Small Clojure Interpreter) in the middle, but that's beside the point, no? https://github.com/babashka/babashka https://github.com/babashka/sci

Babashka's interop is with Java, since Babashka uses a Graal-compiled version of the JVM. It's still the JVM, just baked down. This is different from interop with the native world. It's different from the host runtime actually being native, rather than a baked down version of a whole VM. Graal's native images blur the line between the JVM and native, I would not say Babashka has a native runtime. Perhaps borkdude wou…

Look at the latest babashka release https://github.com/babashka/babashka/releases/tag/v1.12.218

What do you see in Assets? Native executables, specific to an OS + CPU architecture combination, like Linux + AMD 64 or MacOS + AArch 64.

Re: My thoughts after using Clojure for about a month

#195

Earlier quoted context omitted.

"Until you get better" is such an arrogant take. It's not just about skill. It's about maintainability, ease of refactor, and modeling invariants in your code in a way that they can be checked by the machine (the compiler) without every single developer having to maintain them in their head. Clojure even knows this is an issue and many people use `spec` to sort of retrofit static typing. Dynamic typing was, is and al…

I too agree that "until you get better" isn't a good take. To err is human, and even the most experienced developers make mistakes. That said, you don't get static typing for free. As with many things it's a trade-off: you catch some errors at compile time in exchange for working within the confines of the type system. The ultimate hope is that the time you spend fiddling with types is going to be less than the time…

The problem with your counter-argument is that it hinges on a false premise: That you need or even want a function like `assoc` which is polymorphic over everything. It's an extremely overloaded function which does a lot of things at once, and in many circles and arguably in general within the realm of software design, this is considered a smell.

In practice, what you want is something that allows you to do this safely for the concrete type you're working with. If you want an abstraction that covers all of it, there are ways to achieve this in a type-safe manner, such as traits/type classes. Even in clojure, you're not working with everything at once all the time. You are working with a record, or a vector, or whatever. The fact that you can use one function for all of them is mostly just needless cleverness. In Clojure, you have to keep the type of the data you are working with in your head at all times, because even though `assoc` "just works" for many cases, that's not true in all cases. It will happily insert an integer key into a record without issue, which may or may not be waht you want. But you can also try to insert an atom key into a vector, which then crashes loudly. This is clearly an asymmetry in the abstraction.

Moreover, pointing out that Haskell cannot do what you'd want to do in this case doesn't make a lot of sense. I mentioned Haskell precisely because its type system is extremely powerful and complicated to understand for a lot of people, but still doesn't achieve the kind of flexibility we are looking for - it lacks row polymorphism.

To answer your actual question: Typing a function like that for the individual cases is bordering on trivial in a language such as typescript. For the record case, you don't even need it, because in practice, you get the correct type inference for free by just spreading one object into another.

Re: My thoughts after using Clojure for about a month

#196

> The seq abstraction, for example, means I usually don’t have to worry about what kind of sequence I’m dealing with Eh? That's completely lifted from CL ( https://www.lispworks.com/documentation/HyperSpec/Body/t_seq... ). Same for AREF/NTH, there's ELT. Other than that, I agree, CL is baroque yet needs some hole filling here and there. > Lisp: everything is a list But that's wrong. Not even a little. Unless you mean…

> Eh? That's completely lifted from CL.

Clojure’s abstraction is a bit more far-reaching than Common Lisp’s SEQUENCE, implemented as interfaces rather than types.

A Clojure sequence is anything “seqable” (either implements the Seqable interface, or special case handling for host platform collections), not just lists and vectors. Hash maps, sets, Java Iterables, etc. are all seqable and work with the same standard collection functions.

e.g. you can `(map (fn [[k v]] …) {:a 1 :b 2})`, rather than needing a separate MAPHASH function.

Re: My thoughts after using Clojure for about a month

#197

Earlier quoted context omitted.

Clojure was explicitly designed to be dynamic. It’s a feature, not a bug. https://clojure.org/about/dynamic Until you get better at not making mistakes that the training wheels of a static type system “protect” you from, lean into the REPL as a means to build up small correct expressions into larger ones.

"Until you get better" is such an arrogant take. It's not just about skill. It's about maintainability, ease of refactor, and modeling invariants in your code in a way that they can be checked by the machine (the compiler) without every single developer having to maintain them in their head. Clojure even knows this is an issue and many people use `spec` to sort of retrofit static typing. Dynamic typing was, is and al…

“Until you get better” at pedaling, training wheels can help.

It’s not an arrogant take; it’s arrogant to think you know static typing is a requirement for developing software well.

Re: My thoughts after using Clojure for about a month

#198

Earlier quoted context omitted.

"Until you get better" is such an arrogant take. It's not just about skill. It's about maintainability, ease of refactor, and modeling invariants in your code in a way that they can be checked by the machine (the compiler) without every single developer having to maintain them in their head. Clojure even knows this is an issue and many people use `spec` to sort of retrofit static typing. Dynamic typing was, is and al…

“Until you get better” at pedaling, training wheels can help. It’s not an arrogant take; it’s arrogant to think you know static typing is a requirement for developing software well.

It's not about "knowing" anything. It's about admitting that humans are fallible meat computers that can't hold invariants in their head across thousands or millions of lines of code and possibly an exponential number of interactions. It's using the technology we are capable of building to help us because it's the obvious thing to do. The notion of dynamic typing as an attractive programming model hinges entirely on the hypothesis that it lets you somehow express things that you need or want to be able to express that static typing prevents you from doing, and that is demonstrably false. The `assoc` example above is a perfect example.

Re: My thoughts after using Clojure for about a month

#199

Earlier quoted context omitted.

I too agree that "until you get better" isn't a good take. To err is human, and even the most experienced developers make mistakes. That said, you don't get static typing for free. As with many things it's a trade-off: you catch some errors at compile time in exchange for working within the confines of the type system. The ultimate hope is that the time you spend fiddling with types is going to be less than the time…

The problem with your counter-argument is that it hinges on a false premise: That you need or even want a function like `assoc` which is polymorphic over everything . It's an extremely overloaded function which does a lot of things at once, and in many circles and arguably in general within the realm of software design, this is considered a smell. In practice, what you want is something that allows you to do this saf…

I don't see an asymmetry in the abstraction. Both vectors and maps are associative structures - you can assign a key to a value - the only difference is that vectors have a more constrained keyspace (i.e. ordered, consecutive integers starting from zero).

But that wasn't really my point. Even if we limit `assoc` solely to maps it would still be difficult to type effectively.

For instance, suppose we have some code like:

    (let [m* (assoc m :number 3)]
      (:number m*))
We can see that the return type of this expression is obviously an integer, but what is the type of m*? How do we type m* such that (:number m*) can be inferred to be an integer by the compiler?

Most statically typed languages sidestep this problem: instead of using an open data structure like a map, a closed structure like a record or class is used instead, and these structures must be explicitly typed by the user.

The problem with this approach is that now every record is specific and bespoke. You lose access to all the general-purpose functions that operate on generic data structures, and as records and classes are closed, you also lose the ability to extend them.

This is the ultimate problem with static type systems: you're trading capability for safety. If you're programming within a static type system, there are options that are simply not available or feasible to use.

Re: My thoughts after using Clojure for about a month

#200

Earlier quoted context omitted.

The problem with your counter-argument is that it hinges on a false premise: That you need or even want a function like `assoc` which is polymorphic over everything . It's an extremely overloaded function which does a lot of things at once, and in many circles and arguably in general within the realm of software design, this is considered a smell. In practice, what you want is something that allows you to do this saf…

I don't see an asymmetry in the abstraction. Both vectors and maps are associative structures - you can assign a key to a value - the only difference is that vectors have a more constrained keyspace (i.e. ordered, consecutive integers starting from zero). But that wasn't really my point. Even if we limit `assoc` solely to maps it would still be difficult to type effectively. For instance, suppose we have some code li…

> I don't see an asymmetry in the abstraction. Both vectors and maps are associative structures - you can assign a key to a value - the only difference is that vectors have a more constrained keyspace (i.e. ordered, consecutive integers starting from zero).

The asymmetry lies in the fact that it's an overloaded function that's supposed to do the right things every time, but in some cases, it does what is arguably the wrong thing, silently, and in others, it refuses to do the wrong thing and fails loudly. It's better that it fails loudly, of course, but the point is that the ergonomics of the abstraction is lessened because you can't just assume it will work. You effectively have to keep the types of all the things involved in your head and/or trace them to ensure that you don't run into a crash.

> We can see that the return type of this expression is obviously an integer, but what is the type of m? How do we type m such that (:number m*) can be inferred to be an integer by the compiler?

This is trivial in TypeScript. You can see it in action here: https://www.typescriptlang.org/play/?#code/MYewdgzgLgBAtjAvD...

  const m = { name: "weavejester", active: true };

  const mStar = { ...m, number: 3 };
  //    ^? const mStar: { number: number, name: string, active: boolean }

  const x = mStar.number;
  //    ^? const x: number
> This is the ultimate problem with static type systems: you're trading capability for safety. If you're programming within a static type system, there are options that are simply not available or feasible to use.

This is just not true. It's true for some certain specific static type systems, but not true in general, and that brings me back to my original thesis: You just need a sufficiently capable type system with the right properties - structural/row polymorphism, ish, plus type inference. And also my Haskell point: it doesn't have to be an incredibly complicated type system that is beyond mortal ken. TypeScript is already doing this and it's arguably one of the most used programming languages on earth.

Post reply on HN