
How we secretly introduced Haskell and got away with it - philippelh
https://tech.channable.com/posts/2017-02-24-how-we-secretly-introduced-haskell-and-got-away-with-it.html
======
mybrid
In the 1990s I did research on the efficacy claims of object oriented
programming versus procedural programming. This article bares a striking
resemblance in the claims. Case study after case study showed that object
oriented code had less bugs due to compilers catching bugs, etc. However,
almost every study was similar to this report: it was a re-write from
procedural to object-oriented. There exists strong evidence to suggest that
throwing away ones first prototype produces the same results as to benefits
comparing object-oriented versus procedural programming. The conclusion of my
research was the same as others: unless one is comparing the same project
written from scratch without any sharing of design or code then these studies
claims are correlation, not causation.

~~~
kabdib
Given the awfulness of so many OOP-based frameworks that I encountered in the
90s, I began to seriously consider that OOP wasn't a better paradigm for most
things and that instead it was _worse_ , and simply caused people to rewrite
code to the point that it overcame the procrustean bed of inheritance and
whatever other tools the language of choice was providing.

~~~
curtis
My personal suspicion is that the problem there wasn't OOP, it was frameworks.

~~~
colorint
I think OOP will always run into the problem that it requires a taxonomical
theory about your problem, which never holds up in reality. (This is just
another way of saying, inheritance has to be a forest/bundle of disjoint
trees. Yes, there's multiple inheritance, but I'm pretty sure acknowledging
that is a mortal sin among OO types.) Haskell is slightly better, since
typeclasses are less topologically constrained, but I suspect requiring ANY
categorical theory about your problem is going to create mischief in the long
run.

~~~
goatlover
But then you end up growing your taxonomy ad-hoc. Also, Python does multiple
inheritance sanely, and so does Eiffel.

~~~
pekk
Python might do nearly as well as any language could do with multiple
inheritance, but "sanely" is relative, like finding the sanest way to drive
two cars at once.

~~~
dragonwriter
Multiple inheritance is perfectly sane, what isn't (or at least is less) sane
is implicit invocation of methods defined in interfaces (and inheritance from
a parent class is, among other things, taking an interface with a default
implementation.)

Unfortunately, explicit interfaces were a very late idea (at least in terms of
implementation in a major language, not sure if the idea was around earlier)
in OOP implementation, and wasn't on v1 of any major language as a core
approach, so we've got a bunch of languages where we accept multiple
inheritance being messy or we don't have MI at all to avoid it being messy.

But if you had exclusively explicit access to interface (including inherited)
methods, MI would be clean.

------
daxfohl
I'm just starting with Haskell and PureScript. So far I'm liking the latter
better. It solves a few of their gripes with respect to strings, laziness and
records, plus has a more granular/extendable effects system and cleans up the
standard typeclass hierarchy. Also `head []` doesn't crash.

Of course Haskell is more mature, has support for multithreading and STM,
compiles to native, so it's more performant. But PureScript integrates with JS
libraries and seems "fast enough" in many cases. I think it's more interesting
_as a project_ too: the lack of runtime and laziness means the compiler code
is orders of magnitude simpler than Haskell's, so I could see a larger
community building around it if it catches on.

Given that they were on Python earlier, I wonder if PureScript would have been
a better choice.

~~~
chowells
I can't imagine using a haskell-like language without laziness. It's what
makes it possible to actually write small reusable functions.

Tell me, have you ever used foldr in Purescript? It just doesn't lead to
reusable logic there, so I have no idea why you would.

But in Haskell, foldr is used everywhere. Laziness means that logic built with
it is actually reusable.

~~~
snowcrshd
> I can't imagine using a haskell-like language without laziness. It's what
> makes it possible to actually write small reusable functions.

I don't understand your point. Why is laziness a requirement to write small
reusable functions? Are you thinking about currying?

OCaml is (relatively) similar to Haskell and is not lazy. Function currying
does not require laziness.

~~~
the_af
It's unrelated to currying.

It's also hard to explain, but if you're used to Haskell, working with an
eager-by-default language such as OCaml or ML is mildly annoying. You can
adapt, of course, but it does seem as if gluing stuff together is trickier
with eager evaluation.

There are downsides to lazy-by-default, of course.

------
sid-kap
The comparison between Stack and Python build tools is striking:

> No messing around with virtualenvs and requirement files. Everybody builds
> with the same dependency versions. No more “works on my machine” and “did
> you install the latest requirements?”.

I wonder why the Python ecosystem, which is much more mature, doesn't provide
a build tool as delightful as Stack (which is less than 2 years old).

~~~
chowells
Probably for the same reason I greatly prefer cabal to stack. Stack assumes it
knows better than me. Cabal just does what I tell it to do. As a domain
expert, I greatly prefer the latter. It does what I want, nothing more,
nothing less. Stack is a mysterious "solution" to a problem I don't have that
works by doing everything differently than I do.

Stack was created because not everyone is a domain expert. A lot of people
don't want to be domain experts. They just want something that works without
having to know all the details. It was only able (in the business sense) to be
created because so many people look at Haskell skeptically anyway, and take
any excuse to back away from it. The people behind the development of stack
also run a major advocacy initiative trying to get people to use Haskell, so
they found it to be an important thing to build.

You don't need to try to get people to use Python. It's already broadly
accepted. When people run into trouble, they just say it's the price of using
Python, and aren't willing to make the exchange of giving up power to get rid
of a minor inconvenience. So there's no business incentive in the Python
ecosystem for making the tradeoffs stack does in the Haskell ecosystem.

~~~
baldfat
Cabal made me never use Haskell every again. I work in two different locations
and at home. All three locations never worked the same and all had different
issues with Cabal. After hours and hours of trying different things I walked
away into the wonderland of Racket.

~~~
arianvanp
They're working really hard on improving it though . Cabal 2.0 will have a
nix-style build system, in which multiple verions of the same dependency can
be installed globally (so no separate sandbox per project). This will solve
most problems of where cabal breaks down. This gives us almost the same
usefulness as Stack. However, you will have to make sure that there is
actually a feasible build plan, by setting up your version bounds correctly.
With stack, other people take care of this for you, and you never touch the
version bounds, which is relaxing but also gives you less control.

~~~
sjakobi
A nice feature of stack that cabal AFAIK will not provide is that it takes
care of installing GHC in multiple versions. I think that's very important for
newcomers.

------
slezakattack
"It is said that Haskell programmers are a rare species, but actually the
majority of developers at Channable had used Haskell before."

Could you imagine if this wasn't the case? The hurdle to actually get people
excited about a language such as Haskell especially moving from something like
Python would potentially be huge. Kudos for already having that problem
solved.

~~~
arianvanp
We're based in Utrect, where Haskell is part of the mandatory curriculum at
and the language of choice for many master courses. Because of this, almost
all our developers are somewhat familiar with it, or have at least had one
course in it in the past, which really helps a lot!

------
gaius
At one company I experimentally wrote OCaml and named the resulting native
binaries whatever.py. None ever looked at them. So there is alot of scope for
shenanigans.

------
Twirrim
While it's an interesting look at a change you introduced, that blog title
might not come across quite as intended.

If you're having to "secretly introduce" tech, and "get away with it", that
suggests there are unnecessary and unproductive constraints on your work;
maybe even suggesting that you'd get in trouble for actually daring to make
things better.

~~~
arianvanp
The "secretly" part refers to part of the story where we had 1 hour to build
up quick prototype to pitch to our boss. The title is a bit tongue-in-cheek.
It's not that we built this thing in secret for months and then just deployed
it. That's also not what the article says :)

We had been planning on replacing Scheduler for a while now, and had already
written down some mumblings about what the new design should look like. We
were also already discussing whether we would switch away from python back
then.

I think the exact opposite of what you are saying is true. We got the freedom
to experiment with something new, and to actually make things better along the
way.

~~~
Twirrim
>I think the exact opposite of what you are saying is true. We got the freedom
to experiment with something new, and to actually make things better along the
way.

Sure, and from what I read I mostly took it that way. My original point was
just that maybe a bit of caution would be good in the choice of title. If I
was just skimming through the titles on HN, or skimming the article, it could
be easy to get the wrong impression of channable.

------
framp
Glad to see Haskell used in production.

It's kind of funny that build reproducibility (which was a major issue before
stack) is one of the strong point.

I wonder if, for your project, using cloudhaskell would have been more
appropriate. I have a feeling some of the problems you found could have been
solved with that.

------
lima
You _can_ deploy Python code as a static binary that includes the interpreter
along with all dependencies. I heavily use this in production and life is
great - deployment means copying one single binary, reverting means running an
older one instead. No external dependencies, no pip upgrades, just libc.

[https://github.com/pyinstaller/pyinstaller](https://github.com/pyinstaller/pyinstaller)

------
techman9
> No messing around with virtualenvs and requirement files. Everybody builds
> with the same dependency versions. No more “works on my machine” and “did
> you install the latest requirements?”.

While this is nice, of course, I'm not sure that is outcome is unique to
Haskell/Stack. It seems like you could accomplish a similar level of
reproducibility by building a Docker image or bundling dependencies in some
other way.

~~~
Ruud-v-A
We are actually using Docker for generating the virtualenv that we ship and
running tests now. The motivation for doing this is being able to control the
environment; we can run tests and build a package on CI, and we can build the
same package locally when CI is down. We don’t use Docker in production.

It is not clear to me how Docker solves the issue of pinning dependencies; I
would rather have a file that states the exact version of every package to
install, than an opaque blessed container image that has _some_ versions
installed, and I do want to have the versions used under source control.
Generating the image would not be reproducible (in the sense of having the
same Python files inside it) without pinning versions somewhere anyway, right?
Or am I missing something obvious?

------
arianvanp
I'm the author of the post. I'll be happy to answer any of your questions :)

~~~
fajpunk
Thanks for the write up! In the beginning you mention that you ran into some
bugs in the Python version that would have been caught by the Haskell type
checker. Can you go into more detail about what those bugs were?

~~~
nerdponx
I'm wondering if integrating MyPy into the build, or even using Cython for
performance, could have helped.

~~~
Ruud-v-A
We do actually use Mypy! I wrote about my experience with it here:

[https://www.reddit.com/r/programming/comments/5x3hdd/channab...](https://www.reddit.com/r/programming/comments/5x3hdd/channable_how_we_secretly_introduced_haskell_and/degynke/)

------
jwatte
Instead of using separate monad transformers, we use a single "World" that
knows how to provide Redis, logging, iOS, and other typeclass instances.

There is a RealWorld that runs on top of IO and a FakeWorld that runs on top
of pure State for unit testing.

This means that we have to wrap every single API into our own "SupportsRedis"
and similar APIs, but in the end I think it's worth it! Unit tests are super
fast and not intermittent at all.

------
yawaramin
Nice article. Out of curiosity, what does your write with? Vim/ghc-mod?
Emacs/Intero? IntelliJ IDEA/plugins? Atom/VSCode etc.?

~~~
arianvanp
I tried spacemacs once but I found it too magic. I decided I should learn
proper emacs instead of running a random playbox of plugins ontop of plugin.
But haven't had the time to properly learn emacs yet.

I have tried various haskell plugins for vim in the past, but they always
tended to break so I gave up fixing my config and threw them all away.

Now it's just plain vim (with some non-haskell related plugins) Next to it I
have a terminal that reruns tests when a file changes : `stack test --file-
watch` . It's simple but it always works.

I'm not sure if the vim stuff got any better lately, I haven't checked. So if
you have any suggestions, please tell :)

~~~
sadgit
Have you tried running tests from stack repl --test? Its so much faster. Id
love if i could use file-watch and the repl together.

------
javajosh
In case anyone was wondering, the 'stack' command in the article refers to
[https://docs.haskellstack.org/en/stable/README/](https://docs.haskellstack.org/en/stable/README/).
Which actually looks kind of wonderful.

------
kisstheblade
Always when I see haskell demonstrations eveyrthing looks like just interface
declarations.

You can do beautiful interfaces with eg. java also. But where is the meat
where anything actually happens? I rarely see that in these posts. Yes I could
look up the source but I don't have time to read through it randomly.

This looks just so nice and stuff just magically works?:

runWorkerLoop :: (MonadBackingStore m, MonadLogger m, MonadIO m) =>
WorkerState -> m WorkerState

And monads to boot! (are monads haskells equivalent of java factories? I kid,
I kid :)

~~~
Ruud-v-A
The runWorkerLoop function logs a few lines and sends out an initial job
request (by enqueueing an event in Redis). It then calls the nested function
`go`, which dequeues one event of a TBQueue (a thread-safe bounded queue),
matches on the event, and calls the right function to handle it. If the event
was not a "stop" event, `go` calls itself to do the next iteration of the
loop. `go` takes a WorkerState as argument, which is how it keeps track of
which jobs are running, and whether there is an unanswered job request.

In reality the signature is a bit uglier, I simplified it for the post because
the point was about effects. In particular we also pass in the configuration,
Redis connection details, and a callback to manipulate the TBQueue.

------
contravariant
> The issue here is that we cannot run runWorkerLoop and runFetchLoop with
> forkIO, because we don’t have the right transformer stack.

Am I understanding correctly that this is because, while you can lift e.g.
runFetchLoop to something of type IO m (), it's not possible to convert use
forkIO on it since it requires an input of type IO ()? Isn't that just a
consequence of the fact that Haskell has no possible way of knowing if your
side effects can be contained in the IO monad?

~~~
chowells
It's not about side effects, it's about bookkeeping. When you have a type that
indicates that you can do IO, but you're also carrying around bookkeeping data
implicitly for things like configurations and data sources, forkIO represents
a hard problem.

If the implicit configuration is updated, there's no way to communicate that
across threads. The same is true with all the other things monadic layering
can provide. How do you call a continuation that points to a different thread?
That doesn't even make sense.

So.. Why lie in your type and pretend that those things all make sense? Why
not make the type explicit about what makes sense and what doesn't? That way,
when someone wants to do something that has no a priori way of making sense,
they're required to define how to handle it, such that it makes sense in their
specific use case. And that's what the post says they did.

All in all, it's things working as designed. Places where you need to stop and
think are set up such that you need to stop and think to use them, instead of
barging ahead unaware of the issues.

------
davedx
Good luck with hiring!

~~~
dualogy
Shouldn't be hard.. Haskell to me so far feels like an ecosystem with way
fewer jobs / freelance gigs than there are eager-to-go-commercial enthusiasts
hacking away in their spare time..

~~~
daxfohl
That is confirmed here: [https://stackoverflow.blog/2017/02/07/what-
programming-langu...](https://stackoverflow.blog/2017/02/07/what-programming-
languages-weekends/)

------
osi
makes me wonder how long they would have lasted had the initial implementation
been done simpler (ie, not in their PyDatalog fork).

------
hota_mazi
> but if we could get it done, there would be no going back

The naïveté in this simple statement is so cute.

The list of concerns is also pretty naïve. The main problem you are going to
encounter with this project is hiring. If you want to grow this project or if
the main developers leave the company, I bet it will get rewritten in a
different language in no time.

~~~
sjakobi
See
[https://news.ycombinator.com/item?id=13784085](https://news.ycombinator.com/item?id=13784085)
for why hiring is unlikely to be a problem for them.

I also encourage you to find _any_ experience report that tells of
difficulties finding the right candidates for a Haskell job.

------
guptaneil
It looks like jobmachine is a private repo, maybe don't include the url in
your blog post :)

~~~
yawaramin
If the URL to a private repo is not secure, then a whole bunch of people
(including GitHub) have a big problem, and exposing jobmachine is the least of
their worries ;-)

~~~
guptaneil
It's not a security problem, or else my comment would have been much more
adamant about removing the link. It's mostly just reader confusion. Seeing the
git clone url led me to believe the project was open sourced and was
disappointed to find that not to be the case.

~~~
Ruud-v-A
We do have a different small tool in Haskell that we are likely going to open
source though :)

------
mehaveaccount
Actually if you wanted a statically typed compiled functional language of
Haskell while keeping the declarative logic paradigm of Prolog, then the
Mercury programming language was made exactly for you!

------
xyzzy4
Haskell is a bad language, in my opinion, because you can't tell what the O(n)
run-time is for any operation. Instead you just have to "trust" that it'll be
fast enough.

More on this:
[https://www.reddit.com/r/haskell/comments/1f48dc/what_does_t...](https://www.reddit.com/r/haskell/comments/1f48dc/what_does_the_haskell_runtime_look_like/)

All of the answers seem insufficient. Basically you can't estimate Haskell
run-time unless you are very familiar with the internal Haskell engine.

~~~
slezakattack
Completely untrue. I'm not a Haskell evangelist (I appreciate it for what it
is) but I thought I would at the very least point out that most of the
documentation for basic data structures (i.e. Data.List[1]) not only are well-
documented but have a link on the far-right side of the documentation site
that directly shows you the source code and it's usually easy to tell what
it's doing. Any developer should be able to grok that code and determine the
run-time complexity.

[1]
[https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-L...](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-
List.html)

~~~
astrobe_
I'm not a C evangelist, but I would point out that most documentation of
standard C function is very detailed and its code is shown in its man page.
Any developer should be able to grok that code and determine its safety.
_Mattaku..._

~~~
sdflkd
They really should be able to.

~~~
astrobe_
You're completely missing the point: people are not computers. They make
mistakes, including while they check things. That's why you don't want to rely
on tests, but rather on formal proof.

------
ctlaltdefeat
There are various mature options for scheduling jobs with dependencies between
them.

Why did you choose to write your own, regardless of the language?

~~~
Ruud-v-A
Good question! None of the existing options that we investigated supported all
of our requirements. In particular, all the arrows that can exist in the
dependency graph are known ahead of time, but the nodes are not. This means
that a job can depend on a job that does not exist yet. (A user can add extra
feeds to download, and the merge job should wait for them, even if it had been
submitted already.) Furthermore we have a few specific constraints such as
“per project, only one of job type x or y may be running at the same time”.

------
vorotato
I'd use haskell if it weren't lazy by default and didn't use Cabal. Aka I'd
use OCaml/F#.

------
eternalban
> Our lead developer Robert usually comes in a bit later, so we had about an
> hour to build a working prototype.

I'm guessing he is a Python developer and likely he is no longer the lead.

~~~
rkrzr
Don't worry, I'm still here.

~~~
KurtMueller
Yes, but are you still the lead? :)

~~~
rkrzr
I'm still the lead.

~~~
steelbird
0_0

~~~
slezakattack
Well, he's the co-founder of Channable so... :P

