6 ms·
Dueling Rhetoric of Clojure and Haskell
- wellpast 9y ago> Much of the rhetoric that is currently flying around is a false dichotomy. The author here is missing the rhetoric. The rhetoric is not about the programming language but about how we should be doing information processing. Except that the author isn't missing that point: > In Haskell we typically “concrete” data with record types, but we don’t have to. Great. That is the dichotomy. And it's not a "false" one. This is the question: should we be "concreting"? That's the whole dichotomy/point that is being made. By encoding EDN/Clojure in Haskell the author has gone through a cute intellectual puzzle but hasn't contributed to the crux of the discussion. (Indeed, he's tried to dismiss it as "false".) The ergonomics that he ends up with are fairly lean (at least in the examples he's shown), though the Clojure expressions are a little leaner. But that's probably because Clojure has actually taken a stance/belief/opinion on the very real question/dichotomy at hand.
- GreaterFool 9y agoIt's a little bit more than just a cute intellectual puzzle. One could build an efficient EDN library based on that or very similar type. See Haskell's JSON library: https://github.com/bos/aeson/blob/master/Data/Aeson/Types/Internal.hs#L342 https://github.com/bos/aeson/blob/master/Data/Aeson/Types/In... > though the Clojure expressions are a little leaner Yes they are. The price is complete lack of type safety. And the benefit is an insignificantly small reduction in boilerplate code. The number of bugs I've seen where somebody would "get" a number that turned out to be a string or string that turned out to be a number...
- dustingetz 9y agoEDN is not JSON. EDN is Extensible. OP has everyone in this thread arguing about a strawman.
- tome 9y agoThen I think you're really going to need to educate us about what EDN really is ...
- escherize 9y agoEducate yourselves. https://github.com/edn-format/edn/blob/master/README.md#tagged-elements https://github.com/edn-format/edn/blob/master/README.md#tagg...
- tome 9y ago> edn supports extensibility through a simple mechanism. # followed immediately by a symbol starting with an alphabetic character indicates that that symbol is a tag OK, so it's trivial to add that as a constructor to the Haskell EDN type in the post, and you can even support it in JSON with a dictionary like #myapp/Person {:first "Fred" :last "Mertz"} is represented as {"#myapp/Person" : { "first" : "Fred", "last" : "Mertz" } } What are the remaining objections?
- dustingetz 9y agoEDN is much more like XML than it is JSON. 1) When I read #uri "http://google.com" http://google.com" my app code sees (java.net.URI. "http://google.com" http://google.com"), not Tag "URI" "http://google.com" http://google.com" or whatever. clojure.core/map does not see tagged values, it does not know that the values were ever read from edn. 2) Extension happens in userland library code, you don't need to go modify the hardcoded pattern match in core. (Talking about reifying actual instances that we can code to, not reader tags) 3) Data is just information. Information isn't coupled to code, it's abstract values, totally separate from the concrete implementation. As RH says: "code is data. But data is not code until you define a language around it." Typeclasses are about code. 4) EDN values are cross platform and a platform's EDN reader can reify the value into an idiomatic type for that platform. E.g. a haskell edn reader could reify #error "foo" into Left String; a Java reader a Throwable to be re-thrown later. 5) The whole prism diversion is sophomoric. Once you've read the EDN into concrete values of whatever platform type, you can use whatever platform abstractions you like to manipulate them. Clojure has lenses too: http://funcool.github.io/lentes/latest/#composition http://funcool.github.io/lentes/latest/#composition You can watch the EDN talk or read the transcript if you'd like to learn more. This topic is very deep but this thread is not doing it justice.
- marcosdumay 9y ago> This is the question: should we be "concreting"? At some point your program has to do some specific task over some specific kind of data. Maybe you wanted to ask when should we be concreting?
- christophilus 9y agoI wanted to like Haskell, but just never could get to the point where I enjoyed using it. It always felt messy and complicated to me. I think the language extensions were a contributor to these feelings. I also felt as if I spent more time wrangling with the type system than actually solving my business problems. Yet I really do like Clojure, F#, and PureScript. There's an experimental C++ back-end to PureScript now [0]. I wonder if that will ever be a viable production target? Anyway, one of the things I like about PureScript is the row-types. Does anyone know if there's a plan to get row-types into Haskell? [0] https://github.com/andyarvanitis/purescript-native https://github.com/andyarvanitis/purescript-native
- dukerutledge 9y agoThere are a number of row type libraries and a proposed extension: https://ghc.haskell.org/trac/ghc/wiki/Plugins/TypeChecker/RowTypes/Coxswain https://ghc.haskell.org/trac/ghc/wiki/Plugins/TypeChecker/Ro...
- dudul 9y agoI also really dislike this extension system where you can unlock some magical features if you could just know what magical keyword to put at the top of your file.
- nightski 9y agoThe language was designed as a testbed for future PL research. That was the primary goal.
- platz 9y agoI am unconvinced of the practical utility of what row types give you for the added complexity. The proposal to remove the Eff type (going back to IO) from purescript is telling
- wtetzner 9y agoDo row types add a lot of complexity?
- bpolverini 9y agoThe dueling rhetoric is the same rhetoric that has been around for decades: Some people really feel type systems add value; others, feel it's a ball and chain. So which is it? The answer is probably "yes." We should all believe by now since history has proven this correct. Most of the time you start with no type system for speed. Then you start adding weird checks and hacks (here's lookin' at you clojure.spec). Then you rewrite with a type system. I'm a devout Clojure developer. I think it delivers on the promises he outlines in his talk, but I also have no small appreciation for Haskell as an outrageously powerful language. Everyone robs from Haskell for their new shiny language, as they should. Unfortunately, not a night goes by where I don't ask God to make me smart enough to understand how a statement like "a monad is just a monoid in the category of endofunctors" can radically change how I implement marginably scalable applications that serve up JSON over REST. Clojure talks to me as if I were a child. Rich Hickey is selling the case for Clojure, like any person who wants his or her language used should do. His arguments are mostly rational, but also a question of taste, which I feel is admitted. As for this writer, I'm glad he ends it by saying it isn't a flame war. If I had to go to war alongside another group of devs, it would almost certainly be Haskell devs.
- dukerutledge 9y agoI'd gladly storm into the breach with you :) Hopefully we all agree that static types and dynamic types are useful. Those who use hyperbole are attempting some form of splitting. I think the point where we disagree is what the default should be. The truth is this discussion will rage on into oblivion because dynamic types and static types form a duality. One cannot exist without the other and they will forever be entangled in conflict.
- hristov 9y agoWell, I think that static types are much more useful than dynamic ones. Static types allow you to find errors with your program before execution and that is very important. And if you are going to go through the effort of defining types, it is much better to use static types because then you get this additional error checking. Furthermore, with static types the compiler can help in other ways, e.g. by organizing your data in memory much more efficiently. I am not sure what you mean when you talk about the duality of static and dynamic types. One can exist without the other and most statically typed languages either forbid or strongly discourage dynamic typing.
- wz1000 9y agoA dynamically typed language is a statically typed language with precisely one type. It is extremely easy to use haskell in "dynamic mode". Just use `ByteString`(or Data.Dynamic for safety/convenience) for all your data. Types just present a way to encode some statically known guarantees about the structure of your data/code. You are free to not encode any properties if you want to. But it is very rare that the data you are working with requires the full generality of `ByteString`. You usually have some sort of structure rather than just working with strings of zeros and ones.
- brandonbloom 9y ago> A dynamically typed language is a statically typed language with precisely one type. While technically true, saying this is about as useful as saying "You can do anything in any Turing-complete programming language."
- wz1000 9y agoThe point is, types give you an option to encode invariants at compile time. You can choose to use this to your advantage, or not use it at all(use ByteString for everything). With dynamic types(or just one type), you don't even have the option to do this.
- brandonbloom 9y agoExcept I really don't have that choice because the language and library design matters. If I chose to use ByteString for everything, I'd first have to implement Tcl in order to get anything done. But, yes, you're right, most dynamic languages lack good tools for stating invariants and checking them early. I would like to see that change. However, I'd rather the solution account for runtime dynamism, extensibility, and partiality. We're _slowly_ getting there with more and more advanced type system features. It's time to take that knowledge and repackage it at the foundational level of typed languages.
- didibus 9y agoThere's just one type at compile time, but many more at runtime. This is still a strongly typed language. The only problem is our static analysers are too dumb to prove things without providing ample explicit hints, or changing the way we code to restrict certain ambiguities that it can not resolve at compile time. Haskell has chosen to try and push the boundaries of such static analyzer, but there's still limits, and it can't infer everything, and still restricts certain designs. I admire it for its efforts. Clojure has a different strategy, it creates a new time, REPL time. So you can test your types at REPL time. Not when it compiles, but a little before it runs. It won't prove what you don't try though. So in practice, its using a statistical model where the programmer is the heuristic. You best guess the edge cases, and try them at REPL time. This will not catch all static errors, but will also catch some runtime errors. So it creates a disjoint set of errors that it catches. This is a trade off. Static types and REPL time will catch some of the same things, but also different errors. Now both adding static type info, and doing REPL time testing comes to a cost to the programmer. Its one more thing we have to do. Some like me, fond more value most often at the REPL, it helps me explore and innovate my code, and is just more fun to me. I also prefer the kind of bugs it catches. Others think the opposite. What most people seem to agree on though, is that doing both is way too much effort. That's why you don't have REPL time be a popular activity in Haskell, or core.typed be popular in Clojure.
- brandonbloom 9y agoEDIT: deleting this post because it was needlessly counter-inflamatory.
- platz 9y agoNo need for typeclasses/existentials. That is trying to approximate some typed/untyped middle ground. You would just use 'dynamic' if you want true dynamic behavior
- brandonbloom 9y agoThe Edn type given here is closed. That's a correct definition of Edn, which is a closed sum. Edn accomplishes extensibility via the Tag type. However, not all Clojure data is Edn. In order to implement the clmap and clget functions with their full generality, they need to support an open set of types. For example, both `#inst "..."` and `(eval '(Date. ...))` are separate types: TaggedLiteral and java.util.Date respectively. You need either Dynamic or existentials because Clojure enables you to pass data structures between two functions expecting collection elements of differing capabilities without either A) whole program / inter-module analysis or B) an O(N) type translation.
- platz 9y agoRight.
- emidln 9y agoFwiw, you can map over Maps and Strings in clojure: (map identity "foo") ;; a seq for String is its chars ;=> (\f \o \o) (map identity {:foo :bar}) ;; a seq of a Map is the ;; pairs of key/values ;=> ([:foo :bar])
- brandonbloom 9y agoAnd get works on more types, like vectors. It also returns nil instead of throwing: cljs.user=> (get [:x :y :z] 1) :y cljs.user=> (get 5 :x) nil
- weavejester 9y ago"If EDN is an improvement over JSON, then it is marginal at best." Why is it only a marginal improvement? It adds considerably more semantic information. "Utilizing EDN also promotes a lot of invisible coupling. Some may tell you that dynamic types don’t couple, but that is incorrect and shows a lack of understanding of coupling itself. Many functions over Map exhibit external and stamp coupling." Coupling implies a bidirectional connection. Functions rely on data types, but not vice versa.
- grandalf 9y agoThis is great. Why have I never heard anyone make this argument over beers when the debating starts? Seems like critiques of a programming language or paradigm are usually made by someone imagining a very bad codebase from their past.
- aero142 9y agoI recently added Flow Type to a Javascript project and it changed my opinion on this debate. I realized that I really don't care about static vs dynamic typing. What I care about is a hierarchy of the ways that my code can be more likely correct. Given the same level of verification, integration tests and are worse than unit tests are worse than runtime checks are worse than compile time checks. There may be cases where static typing makes code more performant, but I usually care a lot more about development speed and correctness. In this world, I just want a way of verifying my code as quickly as possible. Gradual typing lets me specify some validations of my code that will run at compile time. This is a huge win for me in both correctness and development speed. I don't know if we will ever invent the perfect static type system, but I do know that having the ability to specify some types in a pretty good type system, is better than not being able to specify any types. I'm convinced that a language with a progressive type system is strictly better than one without. Therefor, any debate that compares static vs dynamic, instead of static vs progressive is not interesting to me.
- dustingetz 9y agoEDN (Extensible Data Notation) is extensible in userland, which is the whole point of it. This is JSON plus some extra types. . . . r/clojure on JSON vs EDN: https://www.reddit.com/r/Clojure/comments/6gytlf/json_vs_edn_what_does_rich_hickey_mean/ https://www.reddit.com/r/Clojure/comments/6gytlf/json_vs_edn... Transcript of Rich Hickey EDN talk which OP obviously hasn't seen: https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/LanguageSystem.md https://github.com/matthiasn/talk-transcripts/blob/master/Hi... Transcript of Rich Hickey talk OP linked, C-f "edn": https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/EffectivePrograms.md https://github.com/matthiasn/talk-transcripts/blob/master/Hi... Perhaps OP had his fingers in his ears while he watched it. This blog post should be retracted with an apology.
- tome 9y agoWould you care to expand on that? It's not clear what you mean, neither from your comment nor the linked Reddit post. > Transcript of Rich Hickey talk OP linked, C-f "edn": What do you mean? There are these three occurences of "edn", none of which is enlightening. * That's great, I'll start shipping some edn across a socket and we're done. * How many people ever sent edn over wire? Yeah * So the edn data model is not like a small part of Clojure, it's sort of the heart of Clojure, right? It's the answer to many of these problems. It's tangible, it works over wires It sounds like he mostly cares about edn because of wires.
- dustingetz 9y agoYou have the primary source right in front of you!!!!!!!! What do you need me to explain it worse for? Print out the damn paper, sit down with a highlighter and read. FFS.
- tome 9y agoYou're not arguing particularly convincingly ... If you want to explain what an edn really is the I'm happy to listen.
- AnimalMuppet 9y ago
- tome 9y agoWhat's really sorely lacking from these discussions is concrete examples of functionality that's easy to write in Clojure and hard in Haskell. I don't mean functions like `assoc-in`. I mean real functional parts of programs.
- dustingetz 9y agoIt's about the way you think, not about what you can and can't do. Haskell lets you safely think the really complicated types needed to do programming with zero effects; you couldn't think those thoughts without Haskell because the types we use today are too complex. Clojure encourages you to think in terms of data, to push as much logic as possible out of the code and into the data, and then write simple programs to transform that data. http://hyperfiddle.net/ http://hyperfiddle.net/ (my startup) is an example of a data driven system. Hyperfiddle itself is implemented as a large amount of data + 3000 loc to interpret it. If the system is only 3000 loc, you're really not at the complexity scale where all that category theory gymnastics really pays off.
- tome 9y agoThat's not especially convincing to me. Haskell also encourages me to "think in terms of data, to push as much logic as possible out of the code and into the data, and then write simple programs to transform that data.".
- dustingetz 9y agoc-f "degoes" in this thread
- tome 9y agoYou know John De Goes is a massive Haskell proponent, right?
- enugu 9y agoWatching Hickey's talk, many of the complaints seemed to be valuable, but they didnt seem to be about static types. Rather, it was about some problems with existing data types locking one into a rigid data model when the domain is constantly expanding. This post by DeGoes makes the same point. http://degoes.net/articles/kill-data http://degoes.net/articles/kill-data The default model of algebraic data types is too inflexible. There are different extensions which work with this issue. Good record system, Extensible cases, using free monads etc. We can have concise syntax for automatically declaring an interface for a algebraic data type based on some field values (ie, customizable deriving statements). Namespace qualified keywords, so we have rdf like attribute based semantics. The post doesnt respond to the issue but suggests that if you want to do the same thing in clojure with error handling etc, you will need to think about this stuff. Also, Hickey's mention of Monads was again not about static types. Monad laws are not typechecked. Their motivation is purity. The only slight inconvenience in a dynamic context is that you dont have return type polymorphism, so you have to type IOreturn instead of return.
- dustingetz 9y agoOMG thank you for that link DeGoes nailed it. Data is just information, it doesn't care about it's implementation and is portable across platform and language. You can attach your monads to an #error value when you parse it. Or not, if you happen to parse it in Python.
- tome 9y ago> [Monad's] motivation is purity. This is not true and it's important to clear up this misconception lest anyone thing "the only good reason to use monads in Haskell is because it's a pure language". * The use of monads in functional programming arose purely technically as an innovation in denotational semantics * Then someone noticed you could use it to wrap up IO purely in Haskell * Then it was noticed you could use it for all sorts of other stuff besides dealing with IO in a pure language. Monads are only a little bit related to purity.
- enugu 9y agoYes, sure, monads, applicatives etc. have plenty of applications beyond IO, which is part of why keeping that abstraction separate from any particular use is of value. My point is that this doesnt have much to do with static typing vs dynamic typing per se, as we dont check them statically. They are just important examples of an interface with implementations for many data types, which can be useful even in a dynamic language like Clojure. People who write a parser in a dynamic language might benefit from learning about distinction between applicatives and monads.