37 ms·
Exploratory Haskell (2015)
- hellofunk 10y agoThis is interesting, and I'm curious and respectful of Haskell, but when I read a post like this, while I appreciate many of the strengths of static type checking, it just makes my head hurt to see what this guy goes through to please the compiler, and I'm left thinking that I'd just rather stick to a dynamic functional language like Clojure or Erlang and sketch ideas out more quickly. Maybe I'm just not patient enough for Haskell.
- chongli 10y agoI think the approach taken by the author is simply the wrong one. Writing a huge amount of code without any clue what the types are going to be is just silly. Haskell makes it very easy for you if you work with it in a more interactive fashion, asking it to infer types for you as you go along. One of the best features for doing this is Typed Holes [0]. [0] https://wiki.haskell.org/GHC/Typed_holes https://wiki.haskell.org/GHC/Typed_holes
- Ericson2314 10y agoYeah I always use the type checker to help me refactor too.
- mightybyte 10y agoI think in this case it's a reasonable approach to writing the `dpll` function because that function doesn't actually care about the specifics of the types. This is a nice example of encapsulation and implementation hiding, a concept familiar to object-oriented and functional programmers alike. He could have easily gotten it to compile by stubbing things out as follows: data Clauses = Clauses data Symbols = Symbols data Model = Model isTrueIn = undefined isFalseIn = undefined findPureSymbol = undefined minus = undefined including = undefined findUnitClause = undefined I think it's more a question of personal preference than right or wrong.
- nimish 10y agoThe trick is to realize that the incremental incorrect output you get from the compiler is roughly equivalent to the incorrect output you'd get from the failed runtime. Personally I'd start from smaller functions up but it's not like there's no feedback.
- Ericson2314 10y agoOr rather, you're going to need to fix that code anyways, so might as well use the thing that tells you what to do. I personally find fixing type errors far more enjoyable than debugging, and even if I didn't, I think one can argue that fixing type errors is a more predictable workflow and therefore contributes less uncertainty for time budgeting.
- cle 10y agoIf you're prototyping and exploring, most likely you're not going to need to fix that code anyways, so it would be a waste of time (unless you're already efficient with Haskell dev). I'm a big believer in exploring problem spaces with loose, flexible tools that let you get to the point ASAP and ignore unrelated stuff. If you're then going to build a long-lived, high-availability production system, then take what you learned and harden your design using tools built for long-lived, high-availability production systems (e.g. static typing, formal methods, etc.).
- Ericson2314 10y agoYeah I just don't recall a type error where the low chance of hitting it outways the cost of fixing it. The joy of good languages is closing the gap between prototyping and production, research and practice.
- efnx 10y agoStarting with a large chunk of code that doesn't compile is pretty weird. I don't know why you'd ever have this naturally.
- deleted 10y ago[deleted]
- nilved 10y agoThat's pretty much how Haskell development works. It doesn't compile until it's what you want. TDD in Haskell and Ruby are similar, but in Haskell the T is types.
- dllthomas 10y agoThis doesn't match my Haskell workflow at all. Usually, it compiles every small change, with much stubbed out with `undefined` until I get to it. When it doesn't compile, it's surprising and I learn something about the assumptions I've made.
- nilved 10y agoUndefined and type holes are equally valid approaches
- dllthomas 10y agoTo be clear, I am certainly not saying that my approach is the only (or best) one. Just that when it builds (relative to completeness) is very much at the discretion of the programmer, and despite jokes about "if it builds, it works" not everyone has that dial set all the way to one extreme.
- efnx 10y agoI write Haskell for a living and that's never been the approach I've taken. For me writing Haskell is like a conversation with the compiler, you write a bit, you listen a bit - back and forth - unless you're refactoring you don't go too long without compiling.
- hota_mazi 10y agoYou're setting up a false dichotomy. You can find statically typed languages in-between Haskell and dynamic languages that are less demanding from the developer than Haskell is (my language of choice these days is Kotlin, which offers the best of both worlds in my opinion).
- mightybyte 10y agoI don't understand what you mean here. What in particular did you think he was going through just to please the compiler?
- hellofunk 10y agoIt's an expression, often used to refer to the static compilation development process.
- mightybyte 10y agoYes, I understand the expression. What I'm asking is precisely what things he did that example that he would not have had to do if he were using a dynamic language like Ruby or Python. In other words, I'd like to understand specifically what you think are the extra costs he paid for using a static language.
- runeks 10y agoGHC is there to please you, not the other way around. It enables you to practically turn any runtime error into a compile time error, if you choose your types carefully. I write a lot of Haskell these days, and whenever I'm faced with a compiler error I don't understand, it always ends with finding out that my data model was inaccurate. Two ends weren't fitting together, because I hadn't fully understood what those two ends actually were.
- striking 10y agoI love the concept and idea behind Haskell, and even reading this piece was very cool. But as much as I can sort of guess at what's happening, the steps being taken to simplify the original pseudocode still seem like some kind of space magic. I see stuff like "Learn you a Haskell" and whatnot, but it all seems so theoretical compared to, say, writing a web server or parsing CSV or building a programming language with Haskell. I get monads. But I don't get how to bridge the gap between my knowledge and what I see here. How have other people gotten to this point?
- Parsonsmatt 10y agoWriting code. Failing. Trying again and again. Slowly, at some point, it starts clicking, and before you know it, you're much more productive than you used to be in Ruby or whatever else, and then you're cursed by lambda. I wrote this as an exploratory bit of Haskell before I knew monad transformers : http://www.parsonsmatt.org/2015/05/02/scotty_and_persistent.html http://www.parsonsmatt.org/2015/05/02/scotty_and_persistent.... Three months after that, I had a fulltime Haskell internship, and a year after that, I'm working full time doing Haskell.
- progman 10y agoHow did you overcome the cabal hell? Sandbox? VM? Nix? ...
- nilkn 10y agoJust use stack these days: https://docs.haskellstack.org/en/stable/README/ https://docs.haskellstack.org/en/stable/README/ It's easy to use and completely solves the problem. I could never use Haskell seriously until I found stack.
- deleted 10y ago[deleted]
- progman 10y agoIt may be easy to use but hard to install if you don't want to depend on binary downloads. Since I always prefer source compilation for reproducible installations on new platforms I tried to install an up-to-date Haskell platform on Debian 7 to use stack. After being stuck in the cabal hell again I tried several upgrades and downgrads of ghc just to make a cabal upgrade. Nothing worked. I couldn't even compile the built-in cabal-install of ghc. At least this should work. Haskell is a really nice language, and I admire the work of the developers who provide GHC for free, but installation from source is really immature and painful, at least on Linux. If the Haskell platform were just self-hosting ...
- mrcactu5 10y agosometimes when I get stuck on a small part I let the compiler suggest types. this is different workflow than sitting back and trying to figure it all out yourself.
- quchen 10y agoSometimes I’m not even sure how I developed Haskell pre-GHC-7.8.1 (when typed holes were introduced).
- junke 10y agoThis example does not fit my experience of "exploratory" programming. Here the author implements DPLL from a textbook, and while there is a gap to fill between pseudo-code and Haskell, the problem is already well-defined. Think-upfront-then-code works quite well in those cases. Having compiler feedbacks surely helps to get it right. But exploratory to me means that the problem is not really well defined or understood in the first place. Just as you draft things on paper to get a clear idea of what you want to do, you can use your programming language to express parts of the problem more or less clearly. That also means not being afraid of getting things wrong in the process and being able to code generically. Now, with some languages, you can't explore. I tried in Ada, which I actually happen to like, but every choice was a commitment that would take much time to adapt later, so I actually sticked to pen and paper. I think it can be easier with ML type systems, where it might be easier to have loosely coupled definitions, but I am not sure.
- dllthomas 10y agoYeah, I agree this is a poor example of exploratory programming. I actually do think Haskell does a good job for exploratory programming, though. Whether I'm writing Haskell or C or Python, as I code I'm making decisions about how to represent and organize things. When some of those decisions inevitably turn out to be the wrong ones, I have to figure out what code needs to change in tandem. Tests can help reveal incorrect behavior, but far better to have a tool that can point at the specific inconsistent expressions and tell something about what's wrong with them.
- KennyCason 10y agoI also have never understood the argument “In dynamic typed language X you can just start prototyping, but in static typed language Y you have to fight the type system just to get it to compile” If your types aren’t lined up but it compiles, even in a dynamic typed language, you’re program won’t behave as expected, i.e. you are likely to have a bug. e.g. object + int, and it compiles. How does one really save time by not worrying about these things? I almost always start coding from the core data structures up, even when prototyping. A seemingly opposite approach as OPs post, not saying my method is THE correct method :)
- gizmo686 10y agoI a big fan of strong type systems; but can see where the concept of fighting the type system comes up with people who are used to dynamically typed languages. For example, consider the Haskell Maybe datatype. If you are used to a dynamically typed language, then it may feel like you are fighting the type system to cast between `Maybe x` and `x`. Indeed, in some cases these casts serve no purpose other than satisfying the type system because you, as the programmer, knows that the value cannot be nothing; so not having a cast "ought" to be the same as omitting a null pointer check. However, once you get used to strong typing, you realize that the only way you would get a Maybe type is if (to a first approximation) some programmer thought that there was a chance that the value would be nothing. In this case, it is probably a bug to not deal with the possibility anywhere, and the type system is warning you of this. If you happen to be sure that the value can never be Nothing, then you can use an unsafe cast and make it clear to future developers that you did not forget about the case, but rather were confident that it could never happen. Also, when you are prototyping, you might want to run code even if it has some bugs.
- KennyCason 10y agoI definitely see your point about "too much" enforcement, specifically with the case of error handling. In Java, I could just bubble everything up, default exit-early, and worry about proper handling later. I'm sure a similar argument could be stated for having to create simple data classes vs tuples or dynamic objects, or even number types. So I do agree there is a middle ground here. For example, Java will be friendly towards number casting whereas Haskell is very exacting.
- dschiptsov 10y agoWhile this is a true 80th level hipsterism and snowflackery, it is also a beautiful case to realize that there was reasons behind choosing Common Lisp as the primary language for AI. The Common Lisp code is almost as easy to read as the pseudocode of the book and it is absolutely unnecessary to go through all that type clutter and fancy compositions to satisfy the type-checker. There is zero advantage in doing all this static typing acrobatics. The AIMA supplementary Common Lisp code is definitely worth looking and it is the case study for demonstrating the advantages of dynamic typing. BTW, AIMA python code is also very nice, short and clear but order of magnitude slower compared to the compiled native code state-of-the-art implementations of Common Lisp produce. BBTW, Swift3 port would be really cool.
- CJefferson 10y agoWhile this is indeed interesting, I feel I should point out Haskell is one of the worst languages out there for writing a SAT silver which performs remotely well, as all modern algorithms work fundamentally on in-place modification of data structures, so you just have to put your whole program in the IO monad.
- nightski 10y agoWhy would that make it the worst? The IO monad isn't some dirty thing. Imperative programming in Haskell can be a wonderful experience even compared to traditional imperative languages. I use the "imperative" term lightly here.
- dllthomas 10y ago> so you just have to put your whole program in the IO monad You have to put the bits that actually modify things in IO or ST. While it's better Haskell style to keep as much as pure as you can, when you do need to modify things Haskell still works just fine.
- platz 10y agoThere is no edict that says haskell programs cannot live in IO. Also, it is especially rich for some folks to use 'no true scottsman' without even being a scottsman.
- dllthomas 10y agoThis medium sometimes leaves tone ambiguous... I understand you to be agreeing with me?
- platz 10y agoyes, but also just expanding a bit as well.
- bbcbasic 10y agoNo need for IO guilt! Living in IO saves you from learning transformer stacks early too. Admittedly I find the monad transformer and free monads etc a bit confusing.
- hyperhopper 10y agoI don't use haskell very much, but I'm curious what the use is of defining type Symbol = String Why not just have -> String -> in the function's type? The only thing I see this gains is forcing you to add extra boilerplate everywhere converting strings to "Symbol"s
- chrissoundz 10y agoIt is just a type alias as far as I know. I suppose it adds more 'annotation' to your program, in addition if you decide to change the type of Symbol to be an Int or String String then you have a single reference to change initially.
- the_af 10y ago"type" simply defines a synonym. You use it similarly to how you'd use a typedef in C. You don't need to convert anything, it just gives a more meaningful name to an existing type.
- runeks 10y agoI've realized that all my programming is exploratory. Every time I implement something, I end up knowing more about what I've implemented, enabling me to create an even better implementation the second time. This cycle repeats until I'm satisfied with the result, or I need to go to the bathroom. The real challenge to writing code is understanding the nature of what you're trying to express, down to its smallest, intuitively correct constituents. While you are in this state of understanding, the code writes itself.