Exploratory Haskell (2015)
parsonsmatt.org
parsonsmatt.org
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.
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.
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].
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.Personally I'd start from smaller functions up but it's not like there's no feedback.
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.
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.).
The joy of good languages is closing the gap between prototyping and production, research and practice.
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.
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?
I wrote this as an exploratory bit of Haskell before I knew monad transformers : 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.
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.
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 ...
I will say that I won't touch the Haskell platform with a 10 foot pole. I had nothing but issues with it in the past and it drove me away from using Haskell for years prior to finding stack.
Thanks but in this case I could also download the newest binaries instantly. The point is that I don't want to be dependent on any binaries. Compilation from scratch doesn't work.
Btw Rust has the same problem - it also depends on binary upgrades. Nim (nim-lang.org) proves how easy self-hosting can be - which also means how easy porting to other platforms can be.
http://bitemyapp.com/posts/2014-12-31-functional-education.h...
http://bitemyapp.com/posts/2015-08-23-why-we-dont-chuck-read...
http://bitemyapp.com/posts/2016-02-06-haskell-is-not-trivial...
https://github.com/bitemyapp/learnhaskell
http://haskellbook.com/ by the above author
Building a website is a good place to start. Haskell has several very good web frameworks. "Very good" means fast, correct, and well-designed. I don't recommend Snap for beginners; it uses too many strange idioms that aren't used elsewhere in Haskell.
I personally use Happstack because it's very straightforward; it just gives you a monad in which you can do "web stuff" like inspect the URL, read cookies, send back HTTP responses, etc. You can start doing everything the "manual" way, like passing around state in arguments or doing janky text formatting yourself. As you get fed up with that, you can gradually integrate things like monad transformers, custom monads with typeclass derivation, various high-performance formatting libs like blaze, aeson, heist, etc. If you do database stuff you can learn about weird template Haskell and typeclass stuff with Persistent and Esqueleto.
I found it easier to just start using certain language constructs and libraries, even though i didn't really have the feeling that i understood them. When you know what to use for your problem, open the documentation on it and look at the types. Then play a bit of "fit the type"-game with the compiler. You can already get a long way with that. To figure out which language features or library to use for your particular problem take a look at a book like real world haskell. After reading through a bunch of stuff and asking around a bit you will have a clue whether to use monad X, Y or Z for what you are trying to solve. The knowledge of how to do nice refactering will come over time. This is muscle memory you will train automatically. So maybe you will ask the wrong question when you are focused on refactering code rather than solving your problem. The way the author refactors his own code seems to be new to him also. A lot of people prefer to start out with expressing their problem in the types first, before writing functions.
[1] https://stackoverflow.com/questions/8245288/what-does-data-w...
I hypothesize that if you're still interested in Haskell after that that you may find it easier to learn. Don't know if many people have walked that path, though.
In my view, Haskell is in the opposite spectrum of Rust when it comes to performance over abstraction. Haskell abstracts everything away, and only manages to get good performance because of purity (I would argue), while Rust wants to be close to the metal, preferring performance over abstraction (e.g. with zero cost abstractions).
That is why I suggested it. It's a different "hair shirt" than Haskell, but it's one that's a lot easier to explain the benefits of.
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.
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"sYou 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.
I've written sat solvers, and tried to work with good Haskell programmers to write sat solvers. It didn't turn out well.
Also, it is especially rich for some folks to use 'no true scottsman' without even being a scottsman.
Living in IO saves you from learning transformer stacks early too. Admittedly I find the monad transformer and free monads etc a bit confusing.
Compare that to a language like older Java, where you need to build a class no matter what, most likely you need to implement a few interfaces and add a lot of exception handling just to get the scaffolding in place, and if you want to initialize something as simple as a hash map you have to do it the painstakingly, line by line way. Sure, your prototype may (or may not, depending on how serious you were about handling those exceptions) be more solid, but it took you double the time to get there, which is basically the opposite of what prototyping should be.
With all that said, and being a huge Python fan, I still feel like strongly typed languages should be a requirement for bigger projects that you plan to maintain for years. Python, as great as it is, starts hurting a lot at a larger scale. I'm really looking forward to using something like MyPy once my projects start being Python 3+ by default.
I have had to run vardumps on some large Python/JS code bases trying to figure out what the behavior was because it was unclear what the variables being passed in was. Definitely a pain as the project grows larger. I used to not understand why people who use Ruby/JS/Python were not fans of massive refactoring, until I owned a large PHP code base and saw first hand how hard it was to refactor. Search/Replace only gets you so far. Compared to of course, the powerful refactoring tools you get in a Java language via IDE.
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.
That's the most common method for debugging types in dynamic languages haha. I am all too familiar with it. :)
Though I enjoy simply never having to do this in Java/Kotlin for example.
My method for verifying, even my prototypes, is unit tests.
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.