Clojure to die for
cjohansen.no
cjohansen.no
Who says hard work pays off?
I don't think you could have predicted ahead of time which of those would be more popular before writing them.
Having said that, the Stasis post is much longer, so it's less conducive to reading at work and quickly dashing off a comment or two on Hacker News without your boss noticing you goofing off. Hypothetically speaking, of course. :)
I don't have any problems with the skewed "readers per effort" metrics, it was just a fun observation. If my only goal was internet fame, I would not write huge long-ass posts :)
I think for any big win/viral/hits based endeavour - music, OSS, blogging, games - creating many things quickly and doubling down on whatever works is the best strategy.
HN is pop programming culture and if you're writing Clojure, you can't expect them to appreciate it as much as, say, Node.js.
I've only recently started with Clojure and I feel like a clumsy newb most of the time, but I adore the elegance and simplicity. I like that this article gets into the benefits of hashes, maps, and vectors as first-class objects in Clojure. I'm sure the Lisp purists hate it, but as someone used to 21st century programming languages, I love it. And I like how it shows Clojure's superiority over other languages' access/manipulation of the same structures. I'd love to see a follow-on here about the advantages of immutability too.
As for the popularity of Node.js... fertilizer is a wonderful thing, but it's still made out of manure. I can't quite get around my distaste for Javascript.
In comparison, a post[2] I was really excited about, that demonstrates how to improve (Korma) SQL abstraction in Clojure with Macros (I created a demo app) didn't seem to get anyone excited. I probably spent 4x as long trying to put it together. Oh well.
Anecdotally, it feels like programming-related posts with a few high level pieces of code resonate better than deeper dive posts. Possibly because they're easier to skim across while having your morning coffee.
[1] https://hackworth.be/2014/02/05/an-expanded-comment/
[2] https://hackworth.be/2014/02/18/spicing-up-korma-with-macros...
Based on my experience, a "worthy" article has about a 50-50 chance of making it off the new page to the home page and taking off. This is basically random. Many people think HN is a meritocracy where the best articles succeed, but its not. There is a huge random factor in the way. This isn't too bad for a short throwaway article but it's frustrating if you put hours and hours into an article and know people would like it but it goes nowhere.
[1]: https://github.com/downloads/frenchy64/papers/ambrose-honour...
Interested? Then take a look at the Shen[1] and enjoy having both the benefit of types and other powerful Lisp constructs in one language.
Also ironically, this real implementation of Lisp necessitates being built on "lesser" or not "real lisps" for us commoners, like SBCL and CLisp. It also has hosts on Ruby, Python, and this even lesser lisp called "Scheme" (rest assured, this is a joke and not a troll).
http://shenlanguage.org/Download/download.html
That being said I find it very interesting. Its licensing, even just the word if you read through, sounds very aggressive and weird.
Has anyone here used Shen yet?
The final straws for me is that it is the creator's explicit intent that no incomplete or incorrect implementations be publicly accessible (he among other things thinks there's enough already...), and that the author broke a promise to the community, demonstrating that his word is no good.
I don't think the language will take off unless it change licensing before it's too late, but I still find it enjoyable to use with some smallish projects.
I hope that Shen will get a successor that will make it more suitable for modern world while keeping ideas that make it unique and sound in the first place and smoothing the rough edges.
Sure, Ocaml and shen have "weird" licenses but I just read through them in the last few minutes and see no major issues.
The licenses for both only effect the code for the language interpreter/compiler and associated libraries itself and should have little to no effect on your own code.
If you use MIT/BSD licenses then you are almost certainly fine, with GPL there are some potential issues but most people think that dynamic linking doesn't count as "linking" in the lawyer sense and, therefore, using GPL code with something like Shen would be fine. This hasn't been tested in the courts AFAIK.
It's also explicitly not open source ("We are therefore not open source."), and should you find yourself needing to fix a bug in a language implementation your legal burden massively increases.
Compare this to the licenses you name, they're all well understood, and in at least some cases validated in court. For that matter, the GPL has been validated in at least US and German courts.
As for Ocaml, it uses the Q Public License (as well as the LGPL); it's short and a quick glance didn't indicate it tries to say the same thing more than once. It's been around for a while, to the point it's an OSI and FSF approved licence, so others have looked at it, as they did for Qt prior to version 4.0., and KDE based on it prior to then. About which there was much todo, so it's be thoroughly analyzed.
So it may be "weird", but it's significantly more palatable than Shen's license.
Granted, I very seemingly little about this. So I could be very mistaken. Can some confirm I am wrong? I am sure someone here has far more detail than me generalized nonsense.
Still, thanks for shen, gives me something to read about.
What is this philosophically opposite that you mention? Haskell is good at creating domain-specific languages, so I think the differences must be around the superior meta-programming facilities in lisp languages. So, what would you do with Clojure that you couldn't do in Haskell easily?
- static vs. dynamic typing
- pure vs. allowing side effects
- native vs. hosted
- focus on syntax vs. rejection of syntax
Depending on the problem, you may get big benefits out of one side or the other.
One particular thing that is much easier in Clojure is dealing with heterogeneous, hierarchical data. This is just so effortless because of the combination of dynamic typing and the wonderful built-in persistent data structures. This is possible but takes a lot more work to do in Haskell and is much slower to iterate on when your data changes.
Also, have you used lenses yet? Heterogenous hierarchical data in Haskell is quite easy now.
I think people give Haskell a bad rap for handling heterogenous data, but it's actually quite good at it. You just feel the pain of a poorly specific data domain more sharply and tend to want to reify it into something nicer quickly. No such option exists in Clojure unless you use Typed—and I say this as someone quite familiar with both languages.
example:
function stringify(FooObj object): return object.toString()
Now suppose we do some refactoring somewhere, and we want to call stringify not with a FooObj but with a BarObj. Since we have defined stringify as taking a FooObj, the compiler whines until we update the definition. However, depending on your philosophy, this error is nothing more than noise, because stringify would work fine on a BarObj.
Pragmatists may wish to forgo such safety in favor of greater flexibility:
function stringify(object): return object.toString()
This is the essence of liberal programming: an emphasis on flexibility and minimal specification, at the cost of reduced safety guarantees.
I'm not saying you can't come up with a fair example to support your argument. I will say, though, that it's going to be a lot harder to do so, and that such examples only rarely come up in practice. The argument for flexibility at the cost of guarantees of correctness used to be a good one, but it has weakened significantly with time, and before long will cease to be valid at all.
stringify s = show s
No type definition is necessary, because the compiler can correctly infer the correct, most generic type: stringify :: (Show s) => s -> String
Or, for any s in type class Show, a function that makes s into a String. Note that dispatch is done statically.Modern type systems eliminate most of the cost of type safety through good generics and type inference. Mainstream statically typed languages are just 20-30 years behind the state of the art.
(note that the example actually can be reduced to: stringify = show)
1) The function correctly executes on the class of things for which (.toString thing) has a sensible run time invocation.
2) The function delays examination of the correct class of its argument from compile time to run time.
1 is a benefit, in that in a dynamic system it is possible that something which, at compile time, does not have a sensible .toString invocation, can gain it at run time and then participate in the function.
2 is a cost, in that you have to delay classification to the instant in which you try to invoke .toString
This tradeoff is elemental and there will be systems you can build by embracing it that will never be possible in strongly typed languages. You'll be able to build isomorphs of them, but doing so will involve expressing significantly more ideas to get there.
This is simply untrue. Take an example in Haskell:
> show 23
"23"
> show (4,5)
"(4,5)"
> show (Just 2.34, Nothing, [2..5], 'c')
"(Just 2.34,Nothing,[2,3,4,5],'c')"
This works for all types which are members of the class Show. We can even derive new instances for Show for our own new data types automatically: > data Foo = Foo Int Float deriving (Show)
> show (Foo 3 4)
"Foo 3 4.0"
But what happens if we omit the instance of Show from our definition? > data Bar = Bar Char Bool
> show (Bar 'a' True)
<interactive>:12:1:
No instance for (Show Bar) arising from a use of `show'
Possible fix: add an instance declaration for (Show Bar)
In the expression: show (Bar 'a' True)
In an equation for `it': it = show (Bar 'a' True)
We get all the benefits of safety guarantees with minimal extra work. This form of automatic derivation works for many type classes in the standard libraries but in the cases where it doesn't work we simply define a few methods which are specified in the class's definition. None of this is any more than what you'd need to do in a dynamic language but you get all the extra benefits of compile time safety.According to the Yegge programming axis, Haskell is extremist conservative (emphasizes safety above all) and Clojure is some notches down into a more moderate zone (merely "conservative") that balances safety against pragmatism.
The dynamic-typing means Clojure code is usually built around data-flows of maps/vectors/strings (generic information types that are trivial to integrate from different sources), trivial serialization, code-as-data.
I'd guess Haskell code is more likely to be built around abstractions based on the more powerful type system.
clojure.core.logic - a Prolog-like logic language that you can embed in the middle of a Clojure function - is a good example of something that is possible thanks to Clojure's lisp macros.
import Control.Monad.Logic
And! You don't macros for that. Macros are not used much in Haskell (only for very special cases), there's no need for them.
The real beauty of macros is to simplify syntax without taking a runtime performance hit.
How would you implement thread-first or thread-last in Haskell without a runtime performance hit? Template Haskell?
The type system gets a bit in the way when implementing something like miniKanren in Haskell. I suspect Clojure is a better fit for this kind of highly dynamic problem.
And if you don't like that, it's easy to use "untyped" types as well.
user> (:key nil)
nil
Mmmm, I'm not certain I particularly want this user> (nil :key)
CompilerException java.lang.IllegalArgumentException: Can't call nil
RUN! (:foo (:bar {}))
Without throwing a NPE. Which is what I want 99.9% of the time. As someone who codes in Clojure close to 10 hours a day, I rarely see a problem like you describe. user> (let [e nil] (e :key))
NullPointerExceptionThe link to Clojure points to "http://cjohansen.no/clojure.org" instead of "http://clojure.org".
(map :name people)
evaluates to a lazy sequence, not a vector.
Maybe the other post I posted the same day would give newcomers a better impression of what using Clojure is like in practice (lots more code, more in-depth): http://cjohansen.no/building-static-sites-in-clojure-with-st...
ps: what is the min* function ? can't find anything on google
min* is like min but takes a list, it's not built in. Yes, I know about apply, I still prefer min* to (apply min) in certain cases.
ps: you're a very enthusiastic person ;)
It might be interesting to discuss _why_ keywords or maps or sets can work as functions, by implementing the IFn interface. Perhaps explain why homoiconicity and macros enable things like thread-first and -last, and talk about how we can implement our own reader literals.
That was kinda the whole point of the post.