13 ms·
Thinking in Types
- woah 12y agoSeeing so much stuff about Haskell lately, but there seems to be a curious dearth of actual software written in it, if it's so great. How is it that janky hacked together languages like JS and PHP have huge numbers of projects built with them, while a supposedly superior language like Haskell is mostly academic? If it really makes you that much faster, where are the apps?
- recursive 12y agoOn average, people that don't know much about software development, but want to make software, will do it using a language that has a lower cost to entry. For all the elegance and purity of a language like Haskell, it seems to be completely overwhelming for most beginners. Not all software is created by such people, but I think it explains quite a bit.
- Guvante 12y agoBeginners wouldn't have a problem with Haskell, the intermediates are the problem. If you learned Java or another imperative language, picking up another imperative language is nearly trivial.
- dmunoz 12y agoI understand the critique being made, but this seems like a slightly outdated view of Haskell. There is quite a bit of software being written in Haskell these days. One project that got some press recently is a long existing project that recently went open source, Cryptol [0]. While lots of projects in Hasekll continue to be libraries written for Haskell, there are also lots of languages using Haskell for their implementation language. Browse the GitHub trending repositories listing for Haskell [1] for an idea of what is being done with it. [0] http://cryptol.net/ http://cryptol.net/ [1] https://github.com/trending?l=haskell https://github.com/trending?l=haskell
- m0a0t0 12y agoTo be fair most of the trending github projects are libraries for use in Haskell. There are very few actual applications - there is pandoc, git-annex and hakyll and that is it.
- ibotty 12y agohakyll is a library and not an application, technically, as is xmonad, another famous haskell "application". pandoc is often used as library as well. the point is, that often you have one-off applications that are just a few lines and use these libraries.
- sctb 12y agoThere are some examples here: http://www.haskell.org/haskellwiki/Haskell_in_industry http://www.haskell.org/haskellwiki/Haskell_in_industry
- spitfire 12y agoWell, JS is used because it's the only way to run code on a client system. If there were a byte-code available you can bet people would be using other languages. ASM.js exists, and many languages compile to js at the moment. As for PHP, lots of people still eat at McDonalds. I can't explain why.
- ephemeralgomi 12y agoPartially because McDonalds/PHP lets you eat/deploy by cramming/uploading the food/file directly into your mouth/server. No utensils/tools required.
- virtualwhys 12y ago> No utensils/tools required I think a more apt analogy would be: no furnace to forge your own cooking utensils (hour long build from source to get latest and greatest [7.8] installed) which you use to prepare the meals (wait for your application to compile) that you then stuff into your mouth (deploy to server).
- dllthomas 12y agoThe 7.6 packaged in Debian is perfectly usable and compatible with everything I've wanted to grab from Hackage. There's a few new bells and whistles in 7.8 (like TypedHoles and -fdefer-type-errors) that I'm looking forward to, but their lack doesn't mean I can't build existing code and when developing new code it just means I lack some new tools. Since these tools are lacking everywhere else, their temporary lack is obviously not keeping people away from Haskell. Basically, you can get yourself access to a furnace to forge your own melon baller, but you've already got a spoon, and someone else will be shipping you a melon baller next month.
- cgh 12y ago> Well, JS is used because it's the only way to run code on a client system. Not sure what you mean by this - I guess you're assuming the "client system" is always a web browser? Currently, I'm writing code for a piece of middleware that is a client to a server that models networking equipment. This client then pushes hundreds of thousands of responses into a fast message queue. None of this is written in JS.
- platz 12y agoIt's a good question which I think deserves thought, for now I'll just be slightly tongue-in-cheek and link this: http://www.jwz.org/doc/worse-is-better.html http://www.jwz.org/doc/worse-is-better.html
- mindslight 12y agoTongue in cheek? No, it's completely spot-on. People generally learn by forming patterns from many examples, not by studying the patterns themselves. Attempts to directly communicate abstract patterns generally fails (any school anywhere: "this is boring because we are never going to use it"). Programmers are inherently attracted to building on imperfect abstractions, because that brokenness is something to latch on and set about easily solving (as it's been solved many times before and they don't even need to solve it perfectly). If the abstraction did exactly what they wanted, they would have to recognize that, understand what was given to them, and then confront the essential complexity of their problem that much sooner.
- deleted 12y ago[deleted]
- bilalq 12y agoThe ecosystem plays a big part in this. NPM is actually a pretty nice package manager to work with. Cabal, on the other hand, took me quite some time to setup and use.
- tel 12y agoCabal is actually an extremely nice package manager... it just, like much of Haskell, likes to tell you things won't work far before they fail. In particular, building things with Cabal requires that everything compiles together successfully. This is a much stronger requirement than NPM and has push some more sophistication into the dependency resolution Cabal does.
- freyrs3 12y agoIndeed, if we deferred all type errors and compile failures from Cabal to runtime like Javascript/NPM then nobody would complain about Cabal! I think it's unfortunate that Cabal get's so much unnecessary blame when the problem is often in some other place entirely.
- dllthomas 12y agoThis is mostly true, but I think we do see more dependency related issues than other languages. My working theory is that this is because we wind up building more small, generally useful packages that get used by lots of things and which can therefore be in conflict. I haven't set out to carefully validate this, though.
- jarrett 12y agoI think the package ecosystem is a big barrier. I code on OS X, and it seems like half the Haskell packages I try to install fail to compile. I always get super motivated to do my next project in Haskell, but then give up when I can't install the required libraries. Maybe the situation is better on Linux. In any case, I think Haskell will need reliable package management on at least OS X and Linux before most developers consider it a serious choice for real projects.
- IsTom 12y agoIt's not that much better on Linux. As a fan of Haskell it makes me sad.
- dllthomas 12y agoIt's tractable, but a lot more manual than it should be. It makes everyone sad.
- jarrett 12y agoManual is OK if there's at least a clear path to getting something to install. I don't see that path, though. My biggest issue is that I don't have the expertise to debug an obscure Haskell compilation error. I won't develop that expertise unless I can use Haskell over the long term on real projects. I can't do that unless libraries are available. So it's a chicken and egg problem. I think the same is true for many people who'd like to dive deeper into Haskell. If we can't initially lean on the work of expert package maintainers, we can't ever become Haskell experts ourselves. I believe the developer community could expand very quickly if this problem could be solved.
- dllthomas 12y agoCertainly the case. Much eased (though not eliminated) by the recent addition of cabal sandboxes. There's still no good way to see all the native libraries required by a cabal install, and occasionally there are actual conflicts between packages... I've been meaning to populate http://en.wikibooks.org/wiki/Haskell/Resolving_Cabal_Hell http://en.wikibooks.org/wiki/Haskell/Resolving_Cabal_Hell but have been kinda hoping (almost certainly in vain) that someone with deeper knowledge beats me to it.
- gamegoblin 12y agoFairly popular tiling window manager: http://en.wikipedia.org/wiki/Xmonad http://en.wikipedia.org/wiki/Xmonad
- wting 12y agoHaskell is used in the NYTimes special features group that deploys 60+/yr web apps. http://www.infoq.com/presentations/haskell-newsroom-nyt http://www.infoq.com/presentations/haskell-newsroom-nyt tldw: - RoR shop, too slow and can't afford to scale simply by spinning up more AWS instances. - Haskell type system results in fewer bugs, less downtime than RoR. - Haskell's Conduit library is great for information flow (e.g. scanning Twitter firehose for breaking stories). - Static binaries for easy deployment. - Declarative style and expressiveness helps coworkers understand code quickly.
- virtualwhys 12y agoIt's worth pointing out that the presenter's first language was Haskell and he's been coding in it for over a decade. LYAH won't get you from apples to expert in weeks, much less months; more likely years. Consider me skeptical -- needing to build the latest and greatest of Haskell [7.8] from source on a modern Linux distro (CentOS binary with antiquated libgmp.so.3 dependency, seriously?) is a gigantic PITA compared to virtually every other language where you just download a standlaone binary of latest & greatest from langugage X, modify your PATH, and hit the ground running.
- coolsunglasses 12y agoIMVUs average spin-up time for new engineers that don't know any Haskell is ~8-15 days. I don't recommend LYAH and don't think it's an efficient way to learn Haskell at all. My guide for learning Haskell: https://gist.github.com/bitemyapp/8739525 https://gist.github.com/bitemyapp/8739525 Should get you going quickly.
- dllthomas 12y agoWhy do you need to build the latest and greatest? Building the very latest gcc/clang is also going to be a PITA. In either case, there's a perfectly servicable binary distribution and the stuff packaged in my OS's repo is still plenty usable.
- virtualwhys 12y ago
- badman_ting 12y agoHonestly, it seems to me like if you don't understand category theory and type theory well, using Haskell will be either hard or impossible. That's what people who are into Haskell are into, and they seem to be a relatively rare breed. (I have a lot of trouble understanding these subjects, though I continue to try. I still don't know what the hell a monad really is.)
- mjhoy 12y agoI couldn't really tell you anything about category theory. But I am beginning to grasp monads. (At least in the context of Haskell programs.) I think the important thing is to see how they work, see what they do. btw, you might try this video: https://www.youtube.com/watch?v=o6L6XeNdd_k https://www.youtube.com/watch?v=o6L6XeNdd_k
- coolsunglasses 12y ago>if you don't understand category theory and type theory well, using Haskell will be either hard or impossible Absolutely not true. I never completed a single credit of university and struggled with high school math. I don't know any type theory or category theory. Not only am I comfortable in Haskell, I teach Haskell. Here's my guide for learning Haskell: https://gist.github.com/bitemyapp/8739525 https://gist.github.com/bitemyapp/8739525
- zmoazeni 12y agoJust throwing my voice into the chorus, I know nothing about category theory either, and I don't think that hinders learning Haskell. I do think there is a subset of Haskellers who use category theory to prove certain ideas and they are able to easily express that in code. However I haven't found it necessary to understand the theory behind why something is sound in order to practically use their work in my projects. That says more about Haskell's expressiveness than the target audience to me. Setting aside years of imperative (and also OO) programming and learning a different approach to solving problems has been the biggest challenge for me, by far. That's why I agree wholeheartedly with https://news.ycombinator.com/item?id=7687200 https://news.ycombinator.com/item?id=7687200
- dllthomas 12y ago
- platz 12y agoFrequently there will be many different ways to solve or architect a problem in Haskell, for better or worse depending on your viewpoint. Personally I think it it is better. But anyways, another option would be to make a type for your list which wraps each possible element, then you can just pattern match on the ADT. Also slightly concerned that this has language targeting beginners with almost 0 Haskell knowledge: I am not sure quick anecdotes like this will do more to help than confuse complete beginners. I still think those interested should start with LYAH which does a good job giving enough context with the flurry of new things to learn. I suppose it's inevitable if haskell gets more popular there will be more posts like this (which i like) but I'm not sure if its the right way to onboard newcomers.
- laureny 12y agoI'm getting tired of reading: > This is why we hear that Haskell reprise if it compiles, it works. If this were true then functions would not need bodies, you would just define their signatures and move on with life. The truth is that even with its superb type system, Haskell still needs to run your code. Your code might be statically correct but its runtime is up to you. I would prefer it if people rephrased this claim like "If it compiles in Haskell, it's more likely to run than if it compiles in Java". More honest.
- tel 12y agoWell, as you go to the next steps you can use proof search techniques to do exactly that: write your types and your programs write themselves as the "only possible implementation". This is already possible sometimes in Haskell so long as we restrict ourselves from pathological values like exceptions and non-termination. In fact, the first place this phrase shows up is Russel O'Connor talking about highly polymorphic lens code. http://r6.ca/blog/20120708T122219Z.html http://r6.ca/blog/20120708T122219Z.html This kind of type limitation of possible implementations is called parametricity and is difficult to encounter even in most typed languages as it requires purity.
- dllthomas 12y agoThe first place which phrase shows up?
- tel 12y agoMm, I think I went too far there. I was thinking "confession of a Haskell hacker" but got that conflated with "if it types, it's right".
- laureny 12y ago> write your types and your programs write themselves as the "only possible implementation". But there are only very few type signatures for which this is true, even if you stick to totality.
- tel 12y agoHeterogenous lists are always coming up when people try to translate OO ideas into Haskell. It's great that this example used the barrier of heterogeneity as a reason to think harder about their design instead of barreling forward. In particular, heterogeneity causes a form of information loss via type erasure (existential typing). The problem is that this is pretty heavy machinery and is not always well-suited to such a simple problem as a heterogenous list. Additionally, once you've produced a type like [forall a . Renderable a => a] -- not a valid Hs type, but close frequently you've reduced your options for what to do down to really just a single choice. In this case, `render`. This is what's sometimes called the Existential Antipattern—if you have an existential type where only one way forward remains... you may as well just take that way forward. This is especially easy in a lazy language like Haskell. renderEm :: Ball -> Player -> Player -> [IO ()] -- homogenous! renderEm b p1 p2 = [render b, render p1, render p2] Note that this is functionally very similar to the implementation of `render` for Game as given in the article. In particular, the only difference is that the article's `render` method destroys the barriers between these `IO` actions by sequencing: instance Render Game where render (Game b p1 p2) = sequence (renderEm b p1 p2) where `sequence` sequences a list of monadic actions -- try thinking about this as "distributing" a monad over a list -- much like you distribute multiplication over addition -- -- x (a + b) --> x a + x b -- -- and then go look up Data.Traversable sequence :: Monad m => [m a] -> m [a] sequence [] = return [] sequence (ma:mas) = ma >> sequence mas This tension between a "reified list of actions" and a composite action is pretty much the heart of the expression problem—the reified list is an initial encoding and the final action the final encoding. This kind of thing shows up all the time when dealing with heterogenous lists. The reason being that OO basically favors final encodings to the extinction of initial ones... but initial encodings are what generate type information.
- deleted 12y ago[deleted]
- dllthomas 12y ago"[I]f you have an existential type where only one way forward remains... you may as well just take that way forward. This is especially easy in a lazy language like Haskell." This doesn't seem especially easy in Haskell (in that it doesn't seem harder elsewhere), but especially the case a lazy language like Haskell. In an eager language, (forall a . Renderable a => a) is isomorphic to (() -> IO ()) and subtly distinct from (IO ()).
- jarrett 12y agoOn a related note: The article (quite reasonably) avoids discussion of the graphics library, but I want to know more about that side of things. I wish graphics got more attention in the Haskell ecosystem in general. The options right now are pretty dismal. There is quite literally not a single Haskell graphics or GUI package that I've been able to install on OS X. I'd love to use Haskell to build games or desktop GUI apps, but without a library for OpenGL, windowing, etc., it's not practical. This isn't just whining, though. I bring this up to ask: What can I, as a relatively green Haskell developer, do to improve the situation? Is there a realistic path for a new Haskell user to get involved in the package ecosystem? I'd be willing to bet that a great many developers like me wanted to try Haskell, but gave up when they found out how many Cabal packages don't compile. I think fixing that would do a lot for Haskell's mainstream acceptance.
- sgerrand 12y agoHave you looked at the Haskell QML bindings? They generally work for cross platform requirements. http://hackage.haskell.org/package/hsqml-0.1.1/docs/Graphics-QML.html http://hackage.haskell.org/package/hsqml-0.1.1/docs/Graphics...
- jarrett 12y agoYes. It does not compile on my Mavericks machine. Same with every graphics package I've tried.
- dllthomas 12y agoGet in touch with the developers and work with them to get it building. Often times they don't have access to your platform, so just being that helps. Anything you can do on top of that is gravy.
- jarrett 12y agoSometimes I do. Often, if a Haskell package doesn't have a Github repo, I don't know how to contact the developers or submit a bug report. Is there a standard place on Hackage or in ghc-pkg where one can find that info?
- AnimalMuppet 12y agoI'm not sure that this works in more general cases, though. If I have a more general game with more objects of more types in it, adding each type to the render function is going to get old. In object oriented programming, I'd just call render() on each entry in the list of game objects. But this approach is going to lead me to: - render each entry in the list of Foo objects - render each entry in the list of Bar objects - render each entry in the list of Baz objects - ... which doesn't seem to me to work out very well as the game grows more complicated. (Sorry about all the line breaks - I can't seem to figure out how to get HN to display it right without them.)
- zmoazeni 12y agoThe post links to this page http://www.haskell.org/haskellwiki/Heterogenous_collections http://www.haskell.org/haskellwiki/Heterogenous_collections and one of other solutions is to use existential types to do it. But the author even said: However, I don’t think this is a good use case. We can get around this problem in a cleaner and safer way by using the type system rather than subverting it.
- AnimalMuppet 12y agoRight, I know he said that. But my point is, his solution doesn't scale well to a more complicated problem. (Unless I misunderstood either his solution or your point?)
- tel 12y agoThere are more real solutions which scale better, but in these toy problems it's hard to get to the meat of the problem. Sometimes the "existential antipattern" is a good choice (see Oleg's finally tagless encoding of, say, the linear lambda calculus). Sometimes creative use of static structure can scale much more neatly than lists of concrete objects.
- deleted 12y ago[deleted]
- 12y ago
- dllthomas 12y ago"It’s impossible to run a Haskell program with a type error" Unless you ask for it!
- DanWaterworth 12y agoYes, see: http://www.haskell.org/ghc/docs/7.8.1/html/users_guide/defer-type-errors.html http://www.haskell.org/ghc/docs/7.8.1/html/users_guide/defer...
- sparkie 12y ago> If we’re careful in our module exports, a change like this can be done in a backward-compatible way. Such an approach is outlined here (http://www.yesodweb.com/blog/2011/10/settings-types http://www.yesodweb.com/blog/2011/10/settings-types). The problem with this import solution is that almost nobody actually does it! Nearly every module you will ever import exposes most of the constructors of the ADTs they define - because Haskell encourages it - it's much simpler and cleaner to pattern match over constructors than the functions you use in place of them in order to encapsulate the constructors - which you need to use guards to match over instead.
- mkehrt 12y agoHaskell's module system is kind of weak. Something like an ML variants, with interfaces being defined separately from implementations, is much nicer.
- andolanra 12y agoYou could also pattern-match over them with View Patterns[^1], i.e. export an alternate ADT that is the 'acceptable' view on the data and a function that takes the encapsulated implementation and turns it to the alternate representation—but I have literally never seen this done, save in the documents describing the motivations for View Patterns. [^1]: https://ghc.haskell.org/trac/ghc/wiki/ViewPatterns https://ghc.haskell.org/trac/ghc/wiki/ViewPatterns
- ibotty 12y agoin addition to what andolanra said about view patterns there are also pattern synonyms in recent ghc, which makes this feasible.
- badman_ting 12y ago"A type class defines a set of functions which must be implemented for a type to be considered in that type class. Other functions can then be written which operate not on one specific type, but on any type which is in its given class constraint." Call me crazy but this just sounds like a Java interface to me. Edit: On further thought, I guess the difference is that in Java, the interface itself is a type. So all instances of classes which implement IShowable can be said to be of type IShowable. Whereas it seems that in Haskell a typeclass is not, itself, a type.
- tel 12y agoThere are a few other differences as well. I'm not completely familiar with the ins and outs of Java interfaces, but: * You can instantiate types to classes at any point in time (type definition, class definition, or even orphan instances, though that last category is frowned upon) * Typeclasses indicate typing bounds but do not destroy type information. This means that we can define things like showableId :: Show a => a -> a showableId x = x which allow only showable types to pass but does not destroy type information > showableId (3 :: Int) 3 :: Int > showableId (id :: Int -> Int) !! Type error * Typeclasses can dispatch on *any* type in the signature. This includes the famous "return type polymorphism" but generally means that typeclass resolution involves solving a terminating form of Prolog during typechecking. This means that type information flows forward and backward over judgements and allows for greater inference. * Typeclasses can abstract over higher kinded types. So we can write something like count :: Traversable t => t a -> Int count = getSum . getConst . traverse (const $ Const (Sum 1)) which generically counts the elements in any container instantiating the "interface" Traversable. There's also some even funkier techniques you can use when you start involving MultiParamTypeClasses, FunctionalDependencies, or TypeFamilies.
- sparkie 12y agoAn interface in OOP langs is more similar to the Existential data type which the author is trying to avoid using in this post, because it is sometimes seen as an anti-pattern in Haskell (although some people take this to the extreme and tell you to avoid Existentials completely). If we take for example, a simple IRenderable interface interface IRenderable { void Render(); } There's a bit of boilerplate to add, but we can get something pretty similar in Haskell: class Renderable a where render :: a -> IO () data Render = forall a. Renderable a => Render a instance Renderable Render where render (Render a) = render a The obvious difference between the author's proposed solution and this OOP-style interface is the open versus closed world assumption. By having an interface, we have an open world in which we can easily add new types to render, without changing existing code - only adding new instances. In the solution proposed by the author (and the various other "solutions"), they break the open world assumption and fall back to a closed world - where you need to create specific types which encapsulate all the known types that can be rendered - this is demonstrated by the author's Game and ExtendedGame types - if you need to create a new "ExtendedExtendedGame" type each time you add a new renderable type - this solution obviously does not scale, does it? With the Existential now, we can create a list of Render, which would be similar to having a list of IRenderable in Java. We can't do anything with the list other than call render on each item - which is almost always what we want to do anyway - so the oft-reported "existential anti-pattern" is usually no such thing. In fact, the claim that this is an antipattern stems from the idea that existentials are "type-erasing" - that you might want to convert back from a Render to a Ball for instance (or from an IRenderable to a Ball). Even in Java, this would be a bad idea - it requires an explicit cast which could fail at runtime - you simply wouldn't do such thing unless you were certain of its type, or you guarded such cast by first using the `instanceof` operator. if (r instanceof Ball) { Ball b = (Ball)r; ... } You would not normally do this directly on each type, as you'd be back to a closed-world assumption. Instead, you'd usually create a mapping of types->functions, where you can dynamically test a type and do the relevant action, and you can continue to add new items to the map. In the event we do want a list of renderable items, and we don't want to lose type information - Haskell can also provide a similar type-cast to the one you'd use in Java - it's rightly called `unsafeCoerce` because it is unsafe - as is the Java version, which throws a ClassCastException when used incorrectly. If we modify the existential data type to also include type information, via Haskell's Data.Typeable module, we can encapsulate the bad behavior of unsafeCoerce, and ensure that we only expose a safe version - one that returns "Maybe x" instead of "x, but may fail". {-# LANGUAGE ExistentialQuantification #-} module X.Render ( Render, toRender, fromRender ) where import Data.Typeable import Unsafe.Coerce data Render = forall a. (Typeable a, Renderable a) => Render TypeRep a instance Renderable Render where render (Render _ a) = render a toRender :: (Typeable a, Renderable a) => a -> Render toRender a = Render (typeOf a) a fromRender :: (Typeable a, Renderable a) => Render -> Maybe a fromRender (Render t a) = case unsafeCoerce a of x | t == typeOf x -> Just x | otherwise -> Nothing You can even go as far as emulating Java's instanceof operator (althought not quite the same - it doesn't handle subtyping), just create a function instanceof in a typeclass, and use it as an infix operator. class InstanceOf a where instanceOf :: a -> TypeRep -> Bool instance InstanceOf Render where instanceOf (Render tReal _) tWanted | tReal == tWanted = True instanceOf _ _ = False ... (toRender Ball) `instanceOf` (typeOf Ball) == True
- zwieback 12y agoI think a better example is needed. The example's solution to the Haskell heterogeneous list "problem" could be implemented in other statically typed languages (C++, Java) although it wouldn't be necessary to do so. I don't see how the Haskell type system is safer in this example but I feel there's something interesting there which I don't understand. Can someone explain the advantage of type classes over what could be done with interfaces and templates in C++?
- mcguire 12y ago"...although it wouldn't be necessary to do so." That's actually the point. (Have you read the "wearing the hair shirt" paper?) One of the hardest problems in moving from other languages to Haskell is that many of the solutions that you would immediately default to rely on run-time information or behavior, like the heterogeneous list. Those don't translate well, if at all. Instead, you need to step back and change the problem, by relying more on compile-time, type-level information.