HN user

_halgari

1,207 karma
Posts7
Comments125
View on HN

Same halgari, yes :D.

Yeah he took it from Bagwell, and adapted it, but in general there was a whole discussion way back in the day (~2012) questioning how creating this much garbage by boxing and throw away collections could ever be fast. Datomic is another example: making an immutable DB is a dumb idea right? Well what if storage was super cheap, and almost free? Well then maybe it's not such a bad idea.

So a lot of the Clojure community is based on this idea of taking ideas from way back in the 70's and saying "Well everything has changed, what works now that didn't then"

Oh for sure, it's a bit wonky on the JVM due to the lack of tail calls, but that sort of thing can be done via full-stack bytecode transformation.

But these people are doing this in Clojure, which is quite removed from the JVM bytecode, and talking about how it solves so many distributed problems, which I just don't see happening.

That was my issue for a long time. I even talked with their founder several times on twitter back a few years ago. Each time I was greeted with buzzwords, that I knew the meaning of but I think they assumed I didn't.

They would claim grand things like having solved the issues with continuations and delimited continuations, distributed process migration and a whole host of other very hard problems that haven't been solved in the past. I would ask their founder: "right, so you know that delimited continuations have problems with accidental captured scope, they run poorly on the JVM, how did you solve this, have any papers I can read?", and all I ever got was that Clojure, immutable data, X and Y would fix these issues and you just had to wait and see what they were cooking up.

That's when I knew they had no clue what they were doing. I'm all for pushing the boundaries of tech, but if you're doing something attempted many times before, you at least need a good elevator pitch as to why it's solved now.

I think a great example of this done right is Rich talking about Clojure. People would ask "isn't immutable data expensive to reclaim and allocate". And his reply was always that the JVM's GC was just that good that the benefits to be gained from immutable data outweighed the marginal performance penalty of the amount of garbage collected. What changed since the old lisp days? Well we now have GCs that are super fast and JITs that can optimize dynamic code well.

That's the sort of laser focused vision I never saw from Red Planet labs. You gotta get that problem statement and the solution out early, refine the elevator pitch and be able to articulate to people who know what they're talking about how you're going to succeed where others have failed for decades.

Author here, a few things to keep in mind:

Firstly, I wrote this about 3.5 years ago. I was wondering why people were suddenly commenting on it and now it all makes sense.

Part of the fun of writing articles about this is watching everyone argue about what language can be forced into representing types in a given way. Yes, I assume in any situation that if I want a feature X in a type system, that somehow Haskell can be forced to give me that feature, but that doesn't necessarily mean it will fit with the ecosystem of the language or that that's the only feature I'm looking for in the language. So saying "someone hasn't done their homework if they think X can't be done" isn't relevant, what is relevant that I'm not aware of a language that provides the type system features I want combined with acceptable set of trade-offs.

So anyways, I'll stick around for awhile and see if I can answer any questions. Thanks for the discussion, all!

Why compile ahead of time when you know nothing about what the target platform is capable of? Why compile before profiling to make sure you run the correct optimizations? Startup times are sometimes important, but it's not the end-all-be-all of computer science.

Closed source I understand, they must have something in there they don't want to give away completely for free. But "no commercial use" just cuts out something like 90% of the user base. Because depending on your definition of those terms it would also apply to anyone building anything with it that they plan on selling in the future. Even paid tutorials on how to use it would be out of the question.

It's like "how to not get people to use your tool" 101

Anyone who has done serious performance testing on a DB knows that there's a massive gap between initial findings and a well tuned system designed with the help of the database maintainers. I've seen some nasty performance out of Riak, Cassandra, SQL, ElasticSearch etc. But with each of those, once I talked to the DB owners and fully understood the limitations of the system it was possible to make massive gains in performance.

Databases are complex programs, and if I ever wrote one, it would be infuriating for someone to pick it up, assume it was "just like MySQL" and then write a blog post crapping on it because it failed to meet their expectations.

"Generalization of abstraction" sounds a lot like "a maze of twisted passages, all alike"

That's exactly what I don't like about some languages, if everything is a function, then it's all a big ball of mud. The only thing you can do with a function is call it.

I'd rather have classes of capabilities. Some things are callable, others are iterable, some are printable. But if it's truely about abstraction generalization, that sounds like a mess.

Kind of saw this coming after the third reimplementation in yet another language. And why Rust? 2 years ago they decided to use Rust to implement a new language (IIRC Rust wasn’t even 1.0 at that time). That’s a huge amount of technical risk.

I wanted to be exited about eve, but it was always too light on details, had too much risk to ever allow it to succeed, and after several pivots it failed.

This story probably been a bit different if only it was a bit less ambitious, and a lot more pragmatic.

Well, for the first time since the langauge was created you can now do `brew install clojure`, and then `clj` on the commandline and get a repl. I would have loved something that painless when I was learning Clojure.

Sad thing is...Noir was dead for 4 years before Arachne came along. Go to the GitHub page, the deprication notice is from 2013! And Arachne isn’t even a Cognitect project. The author of Arachne temporarily quit his job at Cognitect to peruse his dream of a ideal web framework.

The number of facts like this that at just plain wrong in the OP just add to the offensive tone. Why should I listen to someone who has no respect for they truth?

What's interesting is that for me Clojure was my first lisp. One of the main reasons I never learned lisp before Clojure was all the parens that made the language impossible to read. Clojure cleans up the "normal" lisp syntax quite a bit, and that made it a lot more palatable.

Beauty is in the eye, and all that, but CL code still makes me want to claw my eyes out.

Maintainable code is very important, unless you work for some company that just produces code then dumps it on a client. The key is to balance functional purity with understandability.

Let's take something like Clojure. I get immutable data, sane multithreading (I haven't had thread related problems in my code for years), etc. All without monads. Whenever I've started doing things like introducing state monads, or seen code involving applicative and "free" stuff, it basically becomes an opaque blob of functions. Zero ability to have the code self introspect or to leverage that pile of functions.

The better method is immutable data and data-first designs. Data is searchable, introspect-able, and transformable. Most CT stuff is all about composing functions via functions. Making your entire system opaque, at least from the point of view of the program itself.

"In particular, algebraic effects are combined seamlessly, whereas monad transformers are needed in the monadic style." Implies otherwise...but to be frank, I don't care. As long as I can get these features without type pollution (as monads do in Haskell) then I'm happy.

It's the design patterns if you are trying to use a 100% pure language, which is a waste of time. Here's the thing...at some point being functionally pure stops paying off. At some point you end up paying more to fit your work into a pure model, then getting any benefit from that model. This is the problem I have with CT. It seems cool and may make you feel smart, but it doesn't actually provide any benefit to writing maintainable code

Every time I've studied Category Theory I've later found a better, simpler, easier to understand, cleaner approach to doing the same thing, perhaps with a little mutation.

And then there's languages like Eff that completely remove the need for stuff like monads: http://math.andrej.com/eff/

So yeah, for me it's been more-or-less a waste of time reading up on this stuff.

I'm the original author of pixie, and yeah, I'm a bit surprised to see this hit HN today.

It should be mentioned that I put about a year of work into this language, and then moved on about a year or so ago. One of the biggest reasons for my doing so is that I accomplished what I was looking for: a fast lisp that favored immutability and was built on the RPython toolchain (same as PyPy). But in the end the lack of supporting libraries and ecosystem became a battle I no longer wanted to fight.

Another goal I had was to see how far I could push immutability into the JIT. I learned a lot along the way, but it turns out that the RPython JITs aren't really that happy with VMs that are 99.99% pure. At one point I had a almost 100% immutable VM running for Pixie...as in each instruction executed created a new instance of the VM. It worked, but the JIT generated by RPython wasn't exactly happy with that execution model. There was so much noise in the maintenance of the immutable structures that the JIT couldn't figure out how to remove them all, and even when it could the JIT pauses were too high.

So anyways, after pouring 4 hours a day of my spare time into Pixie for a full year, I needed to move on.

Some other developers have commit rights and have pushed it along a bit, but I think it's somewhat a language looking for usecase.

And these days ClojureScript on Node.js could probably be made to handle most peoples needs.

And yet, Clojure has less parens than your average JS program. Even Java has more syntactic sugar.

Just compare:

    foo.bar(baz)
to:
    (.bar foo baz)
Same number of parens...they're just in different places.

Sure CommonLisp has a ton of parens, but thats CL and it has its own set of issues.

One more example:

    Runnable foo = new Runnable() {
      Object run(Object bar)
      {
        return bar;
      }

    }
vs
    (reify Runnable
      (run [bar]
        bar))
Less parens, no brackets, no semicolons, and no ceremony. This "parens are killing Clojure" meme is just a straw-man.

I've been a full-time Clojure dev for about 4-5 years now. And the number of times you have to refactor (in the traditional sense) Clojure code is pretty rare. And the reason is that Clojure leverages it's use of immutable data structures, namely hash maps.

So let's say I have a function that needs :name and :addr and it adds a new field calls :name+addr. Now in most (all?) statically typed languages I would have to say that this function takes a "name and addr" and returns "name, addr, and name+addr". So I have a type conversion, right? If the time comes that I need to modify these types, I have to do some sort of refactor.

In Clojure we'd just take a hash map and add a new k/v pair to the hash map we get. Any hash map will work, as long as it has the proper entries, and we'll just add a new entry to that. So if the time comes that I want to call this function with a "company" instead of a "person", it just works, as long as I have the proper keys. And to help check this sort of stuff we do have spec (https://clojure.org/about/spec).

TL;DR -- my belief having programed in both static and dynamic languages is that deep refactoring is primarily driven by the inflexibility of static types.

Same data in Clojure:

    [{:appt/start #inst "01-02-2014T9:15:00Z" :appt/end #inst "01-02-2014T9:15:00Z" :appt/description "See Anderson"}
     {:appt/start #inst "01-02-2014T10:45:00Z" :appt/end #inst "01-02-2014T11:00:00Z" :appt/description "See Lundstrom"}
     {:appt/start #inst "01-02-2014T13:15:00Z" :appt/end #inst "01-02-2014T16:00:00Z" :appt/description "Attend Y Committee Meeting"}
     ...]

Let's point out some important differences:

1) Clojure prefers maps over cons cells. This means that I always know exactly what I'm looking at. I don't have to guess that the second times are the end-times...I know because it' named :appt/end.

2) We don't overload data types. Have a date? Use a date type. In the CL example we see symbols and numbers sometimes used for descriptions, sometimes for times, etc. Here in Clojure we have #inst ... which creates an actual DateTime object. Now I no longer wonder what I'm looking at, I know it's a date.

3) Clojure prefers data over DSLs. What this calendar example shows is some sort of domain specific language that not only has to be parsed by a program, but it also has to be parsed by a human.

4) Got a meeting description, use a actual string....what's up with the use of symbols as strings (I never understood that about CL).

5) From the perspective of a data modeler...in the CL example if you want a meeting to last over midnight or for longer than a day, it looks like you're sunk.

But thanks for the example, it's fun to see how data modeling is done in other languages.

Sure, but what do those functions do? In clojure they manipulate data. That's pretty much the gist of the language: using pure functions to manipulate data.

Almost all the work I do these days as a software engineer is data transformation. Going from a HTTP request, which is data (even the header is a hashmap), and a HTTP body, which is data, into some business logic that eventually writes to a database in a different format.

Even the most complex systems I've built containing dozens of servers and multiple databases, queues, http servers, etc. All boil down to transforming data from format A to format B perhaps with conditional logic applied.

So yes, Clojure is a functional language, but functions are just a tool to be used to get the actual work done of transforming data.

STM and concurrency...sure those are "aspects" of Clojure, Clojure's biggest strength is that it's data-centric. The vast majority of the language is focused around manipulating hashmaps, vectors, sequences, etc, and do that in an efficient way. All the concurrency stuff is just icing.

So I really have to sit back and shake my head when the author says he's going to be as good as Clojure, then goes off into the weeds with custom syntax, STM and actors. Really? Why actors?

This would have been a much better article if it just left Clojure out of the discussion since whenever the author talks about the language he's mostly wrong.

And as always, lies, damn lies, and benchmarks: https://benchmarksgame.alioth.debian.org/u64q/compare.php?la... If you're going to use a phrase like "faster", at least spend some time to define the word.

Can we get some sort of edit to this title to show that it's from back in 2014? A ton of work has gone into Clojure since then and in some aspects the language has gotten orders of magnitude faster (not that it was really that slow to begin with).

Clojure - prioritized rich immutable data. I haven't found any other language that prioritizes open data sets: where a map has "a" and "b" but it could also have "c" and "d", if you only care about "a" and "b" it doesn't matter if the other data just tags along.

And I say "rich" data since other languages are pretty data-centric (scheme of course), but not any that prioritize immutable hash maps, sets, vectors, linked-lists, etc.