Hacker News new | ask | show | jobs
by sid-kap 3399 days ago
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).

5 comments

Stack requires package sets (aka "snapshots"), which some kind of CI system (Stackage: http://stackage.org/) has to do a daily build job to see if they all build and pass tests together. That requires some money to keep running, and buy-in from package authors as there is a maintenance overhead each release. It took a few years for Stackage to get enough packages in it to be generally useful, and then we wrote the Stack tool which was able to default to using Stackage snapshots.

There was (and still is a little bit) of resistance to the whole idea of Stackage from the community; people liked the idea of build plans magically being figured out on demand, it's an interesting research problem (it can be hard to let go of an interesting problem when a solution side-steps it all together). I believe eventually many people changed their minds after experiencing the simplicity of using Stack and not having build hell be a substantial part of their development cycle.

Python would likely have to go through the same process. Although with Stack and Yarn providing frozen builds (and QuickLisp); the idea has some precedence, which makes it an easier idea to sell. I mean, Debian and a bunch of other OSes do it like this, but experience shows programmers don't pay attention to that.

Stack enables application development in Haskell, as opposed to just library development. A proper library doesn't have more than 20-ish dependencies, in my opinion, and manually handling these and their version bounds is not a problem.

But when writing applications with hundreds of dependencies, manually figuring out a mutually compatible dependency range for all packages just isn't an option. At least not if you want to spend time prototyping code, rather than think about dependency ranges.

hpack solves additional problems with the .cabal format (sane defaults as opposed to build failure), and I highly recommend it, for application development at least. I just discovered it a month ago and now I wouldn't be able to live without it.

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.

I am a "domain expert" and that is why I use stack for Haskell projects. It's much preferable to let somebody else handle the burden of ensuring that certain dependencies are compatible with each other.

> Stack is a mysterious "solution" to a problem

There's nothing mysterious about stack. It's just a group of people who step up and say "I am responsible for package $x" and then work together to find stable sets of versions that are guaranteed to work together.

The whole process happens out in the open, for example here is an issue tracking a compatibility breaking change in a common HTML library: https://github.com/fpco/stackage/issues/2246

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.
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.
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.
> relaxing but also gives you less control

More like "leads you to typically exercise less control". You can override versions of packages in a stack snapshot.

Cool I'll give it another go once they release it.
Consider this situation: three different developers are working on the same application. They should all have the exact same dependencies installed, right? Therefore they should be working of of a freeze file of some kind.

Why use an entirely ad-hoc freeze file when you can start from a known-working snapshot (that some of them might already have installed on their machines!) and modify it from there. I find this the perfect option in this kind of situation, and so object to saying that stack is just for non-experts.

The whole "Do you have the dependencies and a Python env installed? Noß Then you can't run this script/program." was one of the main reasons I switched from Python to Rust, where cargo as the (very good) package manager comes with the language and, because Rust is a compiled language, you build all the dependencies into your executable you aren't dependent(heh.) on the user having installed a runtime that maybe or maybe not has all the dependencies at the required versions.
Indeed, Rust + Cargo and Haskell + Stack are very similar in this regard. Both have great package managers, and both produce a shippable executable with only a few dependencies on system libraries. One notable difference is that Stack downloads the compiler, whereas for Rust, every version of the language comes with a compiler and a Cargo. This ensures that you can check out a year-old commit and still build your project with Stack (modulo breaking changes in Stack, which so far I have not encountered), whereas for Rust the compiler version is not pinned.
> for Rust the compiler version is not pinned

Oh, you just need another layer of abstraction! Install rustup, and then (from memory, might be slightly wrong):

  rustup install 1.15.1
  rustup run 1.15.1 cargo build
rustup will take care of getting hold of the right versions of cargo and rustc, and then use them to run the build. I admit that it's not as nice as having the build tool download the right version of everything, but it does work, and you could hide this inside a pretty small shell script or function if you wanted it to be neater.
I have pygradle generate a PEX file for all of my command scripts. Not as seamless as cargo or stack, but it works without rewriting everything.
I should probably have clarified that I wanted the "seamless" way of making the final executable. IIRC, I tried(really hard) and failed on getting some method of "freezing" for Python to work, which made me weary of the prospect of trying something like that in Python in the future(now past).
pyinstaller works great. No issues whatsoever and I'm happily deploying Python 3.6 to machines as far back as RHEL 5.

Single binary to deploy, no dependencies except libc, life is great.

I guess this is true if you don't need to interact with any system libraries.
Could you give an example of what "system libraries" would pose a problem in my example of using Rust with cargo?

Also note that by "no runtime installed" I mean no runtime as in "no Python runtime", "no JVM" etc. not necessarily "no libc"

EDIT: formatting

>Could you give an example of what "system libraries" would pose a problem in my example of using Rust with cargo?

Specifically things like xorg libs, libmpeg, libsdl, and such. Not that Rust would have a problem interfacing with them, just that they would need to be present regardless of whether or not someone was just trying to run a distributed binary.

Agreed that you wouldn't need a VM like CPython or the JVM. However, Rust isn't unique in that department. Almost all languages that compile to binary executables have this advantage.

> Specifically things like xorg libs, libmpeg, libsdl, and such. Not that Rust would have a problem interfacing with them, just that they would need to be present regardless of whether or not someone was just trying to run a distributed binary.

That's why stuff like that is AFAIK usually either distributed with the binary or is absolutely required to have present on the system, regardless of the PL, if you want to/can only distribute a "naked" binary.

> Agreed that you wouldn't need a VM like CPython or the JVM. However, Rust isn't unique in that department. Almost all languages that compile to binary executables have this advantage.

Didn't mean to suggest this is unique to Rust, which is why I wrote

> because Rust is a compiled language.

EDIT: formatting

I think I may have misunderstood your point after re-reading.

I thought you were implying that compiled binaries do not have dependencies. Now I can see that is not the case.

I wonder why the Python ecosystem, which is much more mature

I hope I'm not being too pedantic but Python's ecosystem is much larger than Haskell's, it isn't really more mature. Haskell and Python are very similar in age as languages go.

Maturity comes from:

* Millions of person-hours being poured into a language...

* ...Over a long enough time period that the language can go through several develop-eval-improve cycles - that take real world use cases (And not one-liner bubble sort implementations) into account.

In this sense, it doesn't matter whether or not Haskell was invented in 1890, or 1990. #2 is required for maturity, but so is #1.

(I am not a huge Python fan.)

Absolutely. In fact, one of big things holding back Haskell has been the immaturity of its libraries (because for a long time they were built by hobbyist and academics, with little industry support, in a language where lots of things were new and old architectures didn't work well). Happily that's now mostly behind us: https://github.com/Gabriel439/post-rfc/blob/master/sotu.md (editor support being the main exception)
You can't get 9 women together and produce a baby in 1 month. A lot of developments within a programming language ecosystem originate from new ideas discovered outside of it. It doesn't matter how many people you have working on a project if the crucial piece of tech they need hasn't been discovered yet.
No, but you also can't study 1 woman having 9 babies, and conclude yourself an expert on pregnancy. I'll prefer to get my advice from the doctor who studied 9 women, having one baby each. Note what I said about a mature language having to go through several write-eval-improve cycles.

Diversity matters. A language that one person tinkered on for thirty years is far less likely to be useful, then one that ten people tinkered on for three years. Or, in the case of Python vs Haskell, a hundred people who tinkered on it for twenty-five years.

It is important to note that person-hours are not remotely fungible, and some contributions are even negative in the sense of "developing ecosystem maturity".
I don't really know how to compare.

Python's ecosystem is certainly much more complete, and stable in the sense that radically new concepts don't appear every day.

Haskell's ecosystem is more reliable in the sense that this feature you are using will probably not disappear in a year, and libraries have less conflicts.

I think newer often tends to easier because it has the benefit of hindsight in this case. Also the old tools tend to get more complicated as time goes on.