7 ms·
Clojure's core.typed vs Haskell
- zeckalpha 13y agoI'd be curious to see how Typed Racket compared, too.
- FireBringer 13y agoPretty close to clojure, problem 1: https://gist.github.com/anonymous/6764863 https://gist.github.com/anonymous/6764863
- ambrosebs 13y agoThere would be annotations in the same places as Typed Clojure, except :no-check would be replaced by type soundness-preserving runtime assertions. There would be no need for :no-check anyway; Typed Racket's base annotations are complete AFAIK.
- axle_512 13y agoIt is a little clearer to me how this works with things like primitives, but how do you annotate a function that takes a map as input and returns a list? (defn my-keys [m] (keys m)) Edit: Just found Seqable. Looking into it now. This link helped me a lot. https://github.com/clojure/core.typed/wiki/Types https://github.com/clojure/core.typed/wiki/Types
- adambard 13y agoAmbrose pointed me at this link for documentation of core.typed's convenience type wrappers (Vec, Seq, List etc.): http://clojure.github.io/core.typed/#clojure.core.typed/Seq http://clojure.github.io/core.typed/#clojure.core.typed/Seq
- ambrosebs 13y ago(ns my-ns (:require [clojure.core.typed :as t :refer [ann]])) (ann my-keys (All [a] [(t/Map a Any) -> (t/Coll a)])
- axle_512 13y agonice, thanks!
- masklinn 13y ago> As seen at the top, it was necessary to annotate mod. It has the no-check flag on it, which is basically how you tell core.typed to just take your word on this one. That's something you can't do in Haskell, but I'm not sure whether or not that's a good thing. Isn't the reason why you need to do it because you're importing a non-typed symbol? Wouldn't the only situation where you'd need to do that in Haskell be at FFI? (and a nitpick: `putStrLn $ show` can be replaced with a simple `print` call)
- rmcclellan 13y agoOf course you can do this in Haskell: http://www.haskell.org/ghc/docs/7.6.2/html/libraries/base/Unsafe-Coerce.html#v:unsafeCoerce http://www.haskell.org/ghc/docs/7.6.2/html/libraries/base/Un...
- brandonbloom 13y agoI'll save the value judgement for another time, but I'd like to point out an important difference: As the word "unsafe" implies, these Haskell primitives forego type safety in addition to type correctness. That means you can get segfaults and other undefined behavior at runtime. Such a type error on the JVM will simply produce an exception at runtime.
- tikhonj 13y agoThis is why we have Data.Dynamic which does safe dynamic types. It almost never comes up because it turns out to be virtually useless, but that's a story for another time.
- brandonbloom 13y ago> it turns out to be virtually useless That's debatable, however Data.Dynamic is built on top of Data.Typeable, which provides a lower level runtime type safety facility. I think we can both agree Typable has lots of interesting uses.
- lelf 13y agoApples vs vacuum cleaners
- freyrs3 13y agoA simple program is an interesting comparison, but it's kind odd to consider this to compare the whole typesystem. It's hard to understate that GHC has a /big/ typesystem and has had a significant amount of research done in it over the past 10 years and a a result there are a lot of very powerful non-trivial typing constructs. I think the real testament to the power of Haskell's typesystem is how well inference still works even in the presence of most of these non-trivial extensions.
- adambard 13y agoReally, the post is about Clojure; these toy problems represent the breadth of my experience with Haskell, so I'm not really qualified to go into more depth. Certainly Haskell is said to have the most powerful type system going, and I don't see any (mainstream-ish) language displacing it any time soon. Still, I think core.typed does pretty damn well for being a standalone library.
- acomar 13y ago> Certainly Haskell is said to have the most powerful type system going, Nah, Coq, Agda, Idris and any dependently typed language have Haskell beat -- their type systems are designed to be just as expressive as their value language. Haskell is certainly moving in this direction, especially with modern extensions. The issue is of course type inference breaks down in the face of these extensions, and Haskell is trying to maintain its powerful type inference as best it can. Certain combinations of extensions already force type annotations. > Still, I think core.typed does pretty damn well for being a standalone library. This is true, and I think the comparison to Haskell actually hurts the greater point you're trying to make. Haskell has full static types at its disposal, and comparing them to the optional typing of Clojure will always leave something wanting -- why draw that comparison when competing with Haskell is not the intention of the library?
- chongli 13y agoNah, Coq, Agda, Idris and any dependently typed language have Haskell beat -- their type systems are designed to be just as expressive as their value language. That may be true, but it comes at a big cost: inference and decidability. Haskell's type system is designed to very carefully butt up against the limits of these features. Once you go full-spectrum dependent types, you lose all that.
- jahewson 13y agoThe Haskell could use a list comprehension... divBy x y = y `mod` x /= 0 divBy3or5 x = divBy 3 x || divBy 5 x euler1 n = sum [x | x <- [0..n], divBy3or5 x] main = print $ euler1 1000 Personally, I'd switch out lines 2 and 3 to: euler1 n = sum [x | x <- [0..n], divBy 3 x, divBy 5 x] Compared with the Clojure (correct me if I'm wrong): (defn euler1 [n] (reduce + (filter (fn [x] (or (div-by 3 x) (div-by 5 x)))) (range n))) Edit: Note - Clojure code doesn't fit into a HN one-liner.
- adambard 13y agoYou could shave off a precious few characters using the function reader syntax: (defn euler1 [n] (reduce + (filter #(or (div-by 3 %) (div-by 5 %)))) (range n))) Or if you're into comprehensions: (defn euler1 [n] (reduce + (for [x (range n) :when (or (div-by 3 x) (div-by 5 x))] x)))
- segmondy 13y agoWhat a waste of an article. 8 lines of code for haskell, 19 lines for clojure. The problem is easily solved in one line. I seriously don't care that's he comparing core.type, when you start trying to write langauges the same way, you lose the advantage that they provide. 1 line of python. print sum(filter(lambda x: (x % 3 == 0) or (x % 5 == 0), range(1, 1000))) clojure (apply + (filter #(or (= 0(mod % 3)) (= 0(mod % 5))) (range 1 1000)))
- tieTYT 13y agoIMHO, your code is much harder to read than his. It looks like its been run through a minifier. I'd rather maintain his code.
- adestefan 13y agoThe clojure code is pretty idiomatic and clear to anyone that's done work in lisp-like languages.
- awj 13y agoAs someone who has done work in lisp-like languages, I would call the clojure code legible but not "clear". It's perfectly fine for a one-off solution, but I wouldn't consider it acceptable for anything I planned on coming back to in a few months.
- tieTYT 13y agoI don't know when I cross that line of someone who's done work in lisp-like languages, but I've made two or three small hobby projects in clojure and contributed a bug fix to http-clj and am well versed in the source code of seesaw. I don't usually see code like that in those projects.
- Fishkins 13y agoIn Haskell it could be sum $ filter (\x -> mod x 3 == 0 || mod x 5 == 0) [1..1000] but I don't think that detracts from the article. He said up front his solution was over-engineered. That's nice in this case, because it lets us see how the type annotations look for very small, simple functions so we can get an idea what they looked like. I know some Haskell and Clojure, but I'd never actually looked at core.typed. This was a nice, basic intro to it.
- adestefan 13y ago> Integer is less useful as a type than hoped. AnyInteger seems to be more permissive, and works more often. I'm sure there is a good reason for this. This is with about 15 minutes of looking at core.typed. Isn't this because the type Interger is actually java.lang.Integer, but (type 1000) is java.lang.Long? Also, AnyInteger isn't that permissive. It's defined as (U Integer Long clojure.lang.BigInt BigInteger Short Byte). The big difference would be that Haskell's Int is bounded so AnyInteger is more like Haskell's Integer.
- adambard 13y agoThat would be the reason, yeah. I had forgotten that Clojure tends to use Longs for everything.
- tikhonj 13y agoIf you really want it, you can get safe dynamic typing in Haskell using the Data.Dynamic module. However, since you don't really want it, it's a library that isn't introduced in basic tutorials. This gives you a pragmatic equivalent to no-check, but is almost never used in practice because it turns out to be unnecessary.
- tel 13y agoEvery now and then I reinvent Data.Dynamic. By this point I'm pretty far into the realm of "probably not worth it", but hitting Data.Dynamic is usually an unignorable indication that I'm going to regret this code.
- dmytrish 13y agoAlso, GHC from version 7.6.1 supports a flag to defer type errors until run time[1]: -fdefer-type-errors. This is not support for dynamic types, but still gives even more flexibility in type-error handling. [1] http://ghc.haskell.org/trac/ghc/wiki/DeferErrorsToRuntime http://ghc.haskell.org/trac/ghc/wiki/DeferErrorsToRuntime
- IsTom 13y agoNot being familiar with core.typed: does it support parametric polymorphism? Can you dispatch based on types (e.g. typeclasses)?
- munificent 13y agoYes, Clojure has "protocols", which allow multiple dynamic dispatch on generic functions. http://clojure.org/protocols http://clojure.org/protocols
- chongli 13y agoRight, but protocols are not based on core.typed's annotations. There is no way to dispatch on the return type of a function, for example.
- brandonbloom 13y ago> There is no way to dispatch on the return type of a function, for example. A feature, in my opinion. See https://news.ycombinator.com/item?id=6459968 https://news.ycombinator.com/item?id=6459968
- chongli 13y agoA feature, in my opinion. Well, Clojure's a different language so it doesn't matter as much. If Clojure had enforced purity and pervasive laziness (a la Haskell) it'd be a real pain to use monads without polymorphic operators for them.
- brandonbloom 13y agoYou're assuming that Haskell's monadic approach is the ultimate approach to managing effects (which include delayed evaluation). Personally, I prefer the model in Eff [1] and proven as an alternative to monad transformers (although, still a single-layer monad in Haskell) [2]. Eff's approach utilizes capabilities [3], called "effect instances", to restrict access to effects. Much nicer, in my opinion. Hence, I've been experimenting with the ideas in Clojure [4]. [1]: http://math.andrej.com/eff/ http://math.andrej.com/eff/ [2]: http://lambda-the-ultimate.org/node/4786 http://lambda-the-ultimate.org/node/4786 [3]: http://erights.org/elib/capability/ode/ode-capabilities.html http://erights.org/elib/capability/ode/ode-capabilities.html [4]: https://github.com/brandonbloom/cleff https://github.com/brandonbloom/cleff
- lysium 13y agoIs this (at the end of problem 1) (defn div-by [x y] (== 0 ))(mod y x) a typo or a Clojure feature?