Hacker News new | ask | show | jobs
by chowells 4367 days ago
I've never found a language better suited to quick and dirty prototyping and exploration than Haskell. I was part of a small team that built a core web service from scratch in Haskell. We had no idea what we were doing (feature-wise) when we started, and just put some stuff down. Make it compile, see how writing the next bit of code works out. If it's terrible, refactor. Every part of the Haskell toolchain is about empowering refactoring. Not with silly automated tools - with a set of language features that tell you when you didn't think about something.

Even after the service was up and handling hundreds of requests per second (yes, really - it was hardly the company's first foray, and we had a lot of customers lined up to use this as soon as it went live) we often found that the current design was severely hindering a new feature we wanted to add. So we'd just change the design. It wasn't uncommon for an update to require changes to 20% of the lines in 75% of the files. And it was no big deal. The compiler had our backs, and made sure we made all the changes in a way that made sense. We didn't always get it right - sometimes the system tests caught things the compiler missed, and on rare occasions a bug even slipped into production. Such is life in a startup, right?

But here's the thing. At no point in the process was there a monolithic design done ahead of time. In fact, it can hardly be said that there was any designing done ahead of time. Pretty much all the design work was a result of aggressive refactoring when adding new features. The whole process over years of development was nothing more than turning a quick and dirty exploratory prototype into a real, solid production system.

For contrast, an associated service was written using Rails, because we thought it'd be easy for what that service needed to do. It ended up being a nightmare to refactor and add new features. We were just never sure we got all the mechanical cases when something deep needed to be changed. Between the two, there's just no question which system was better when you start out not having any clue what the product needs to be.

1 comments

There is more to quick and dirty programming than refactoring. I would say refactoring even doesn't play much if a roll here. What is key is the ability to be in a broken state for long periods of time while still being able to test and executed pieces of the system. This doesn't work in Haskell since Haskell is not just statically typed, it is Statically Typed! Type inference and code generation are slaves to types, and don't handle errors very well. That could be fixed, but it doesn't seem to be a priority (generate code Forman incorrect program seems to be taboo).

More crucially, Haskell programmers tend live in their compiler and not their debugger. The adage "well typed programs don't go wrong" means you spend a lot of time just getting the code to compile, fitting types together, and not much time seeing how things work together. Much of this is because the community emphasizes the compiler, and in fact good graphical debuggers for Haskell aren't there yet (and even difficult to design given lazy evaluation). Bret victor didn't do his inventing in principle talk with Haskell for good reason!

Python and ruby. programmers live in the REPL and debugger, they don't get to observe type errors early like Haskell programmers do, but they immediately get to see real interactions. I can then see why going between Haskell and ruby would be a disaster.

You can compile haskell programs with type errors (in GHC, anway) if you want. They crash when you attempt to execute code that has a type error, but if that's what you want, feel free to use it.

It's not something I've ever needed. It's got little to do with quickly exploring design space.

Edit:

I guess I should include a couple words about why it's not really necessary in real coding. A side effect of good module boundaries is that you can test breaking changes in isolation in the REPL. Change a few things that are tightly coupled at the same time, load their module, don't worry about fixing any other modules until the first one works the way you like. In practice, this means you almost never need the option to defer type errors to runtime.

"real interactions"? It is disingenuous to imply that all Haskell programmers do is see type errors while Python and Ruby programmers have 'real interactions'.

For instance, today I wrote tons of stuff in the IO Monad and had what you call 'real interactions'.

Also, more time spent in the debugger doesn't necessarily mean a faster working program. The psychology of it is more rewarding, but there's nothing necessarily more "real" about it.