Hmm, okay so Spock looks nice. Neat! The routing code looks very easy to read:
main = do
spockCfg <- defaultSpockCfg () PCNoDatabase ()
runSpock 3000 $ spock spockCfg $ do
get root $ do
Spock.html "<div>Hello world!</div>"
get "users" $ do
Spock.json (A.object [ "users" .= users ])
get ("users" <//> var <//> "friends") $ \userID -> do
Spock.json (A.object [ "userID" .= (userID :: Int), "friends" .= A.Null ])
Alright, let's figure out how this works.
runSpock :: Port -> IO Middleware -> IO ()
Okay that makes sense. Takes in a port, some middleware and spits out IO. Not crazy. Hmm, okay these dollar signs are making things a little confusing, but I can figure out that spock returns an IO Middleware and therefore we can see that spock takes the output of the right do block along with spockCfg. Not the easiest to scan at first glance because of the right to left reading but okay.
Let's look at get.
get :: HasRep xs => RouteSpec xs ps ctx conn sess st
Ah, okay, so uh...this doesn't appear to be a function. Oh, I see, here it is:
type RouteSpec xs ps ctx conn sess st = Path xs ps -> HVectElim xs (SpockActionCtx ctx conn sess st ()) -> SpockCtxM ctx conn sess st ()
Well I'm not really sure what xs, ps or st are. And HVectElim doesn't tell me anything. Path is pretty clear and SpockCtxM is a monad of sorts. But what the hell is HVectElim? Alright whatever, let's just figure it out via the signatures of Spock.html/Spock.json. Which are...nowhere to be found in the documentation. I looked them up in the index and nope, no entries. I'm very confused about what HVectElim is and why that would be a fine name.
I can continue past this point, but you get my gist.
This is actually way better than my other experiences with Haskell and web dev, but it's by no means an easy or fun experience reading this code. Variable names like xs, ps, etc. are great when you're dealing with a generic list but they get old when that's the only documentation (because "type signatures are documentation" is totally infallible). And a name like HVectElim is just baffling. What, did they charge you per letter? Did half your keys fall out? Why is that a good name?
I really want to use Haskell for something useful. But I'm running into pain points trying to understand a glorified Hello World. Now granted I'm by no means a great Haskell programmer. But that's kind of the point. I understand monads. I get functors and applicatives. I should be able to write a dumb Hello World app dammit.
That documentation indeed could be improved, but I guess the intention here is that basic usage is learned from the tutorials. Though it's possible to figure from that documentation alone as well (I'm reading it for the first time too, but using Haskell often): Path construction collects types of arguments into xs, then HVectElim (heterogeneous vector eliminator, or maybe "elimination") unwraps it into an appropriate function type. xs are those types, ps is PathState, st usually stands for "state".
A dumb web "Hello, world!" can be written with just putStr though, using CGI (along the lines of what I've mentioned in another comment here). Or perhaps with Network.CGI ("cgi" package), which is quite a bit more straightforward than that RouteSpec, especially if one wants to quickly understand how it works.
I had a similar experience (wanting to use a language for something useful, but mostly failing to) with Rust, and maybe Python (just s/wanting/having/ in that case), but I guess the problem with that was primarily in trying to find out what's the "right"/idiomatic way to do something while there's none, or to apply practices from other languages. And maybe in not having a suitable project idea at the time. Most likely there can be more reasons of that, but they seem to go away with practice, clear goals, and/or motivation.
Basic is the key issue here. Sure I can type this out and get a simple program working (although if I make a transcription error, I'm not super sure I can reason through the code). But if I want to build anything beyond the basic, I can't rely on the three whole tutorials on Spock^[1]. I need to start understanding the library.
But at that point reading the docs is pretty frustrating. Even with your explanations, I can't see how HVectElim or xs, ps, st are okay names. Sure, it's idiomatic. But that doesn't mean its good. What was wrong with types, pathStates and state? Sure it's less terse, but in this case that's fine. Or if they wanted to keep the same naming, at least explain it in the docs!
Having built a web app in Rust, I've certainly run into pain points, but nothing at this level. A library like Rocket has so much more documentation and careful examples^[2]. But also, the interfaces in Rocket are fairly simple. You write a function that returns a type that can be converted into a response. Anything you can serialize with serde can be converted into a response. With Haskell, the interfaces are that my route defining functions return a
SpockCtxM ctx conn sess st ()
which is actually a
SpockCtxT ctx (WebStateM conn sess st)
With WebStateM being
WebStateT conn sess st (ResourceT IO)
Yeah...I can kinda infer that these are chained monads but I have no clue what bind or return does. I assume SpockCtxM is a reader monad and WebStateM is a state monad? But that should really be described in the documentation.
I see that the other libraries in the tutorial are better documented than Spock, so hopefully the Haskell ecosystem is getting better at this. But I'm still not sure I'd want to build a web app in Haskell (how's package management? Did you guys ever resolve cabal vs stack?)
Longer names can be seen as unnecessary and duplicating information. One has to balance between being verbose and laconic when it comes to naming in general, but judging by the lack of annotations in this case, not much care was put into it being easily readable. It may be useful to report it, once that's an issue: chances are it just seemed obvious to the author, as it also often happens to code in general, and acceptable to users.
If you see an insufficiently documented and/or straightforward API, and there's no good reason to use that one in particular, I think it may also be a good idea to look for alternatives, in any language.
> how's package management?
I think it's fine, but there are different opinions on how it should be. I prefer a better system integration and shared libraries coming from system repositories (which is usable with Debian), Cabal works fine, Stack seems to be popular, some seem to use Nix.
> Did you guys ever resolve cabal vs stack?
"Resolve" as in choosing a one true way to build/install things? I don't think so, but there is this annual "state of Haskell" survey that includes build tools, which was closed just a few days ago this year (but no results available yet, afaik), though there are the results from last year [1]. "State of the Haskell ecosystem" [2] may be of interest too.
There certainly are imperfections/drawbacks (and/or varying approaches/opinions/preferences), and it may not be a good choice for you for making a dynamic website (I think in many cases the overall best choice is the language one is most fluent in at the time), but my replies were mostly with the "I really want to use Haskell for something useful" sentence in mind: being exposed to worse (or at least less suitable) bits and running into a dead end can be unfortunate in that case.
The reason I ask about package management is because when I do my annual try-to-use-Haskell-for-real event, I pick either Stack or Cabal, inevitably run into issues with one and have to switch to the other. Plus I've had situations where I tried to install packages and the manager gave me the computer equivalent of a shrug. Contrast that to Cargo which works beautifully and seamlessly out of the box.
However I hold out hope that Haskell is getting better and easier to use. Legitimately, this tutorial's code looks a lot better than what I've seen before. But I'm still dissatisfied with the level of naming and documentation. I get it that the names may have been obvious to the author, but it's a little worrying to me that the author didn't think about this fact when writing the documentation. Plus Spock isn't a young framework. It's my belief that anybody writing a package should know that interface names are designed to be readable and understandable. And these names aren't subtly hard to read. HVectElim just isn't a great name.
That being said, I don't want to just hate on the Haskell ecosystem. I do really find the language fascinating. I just want some better documentation, more tutorials and nicer, unified tooling.
Stack uses cabal underneath though. What kind of issues are you running into?
Personally I love the language but struggle with all the (necessary) stuff around it. Configs, deployment, dependency management, editor setup, lint, auto-formatting... Everything feels unnecessarily painful.
Generally issues where package management just fails. Also it's just overall not ergonomic. I end up installing yet another version of GHC a lot (I believe that GHC version should be handed separately from package versions like rustup/cargo, nvm/npm, rbenv/bundler). But this info could definitely be out of date. I might be due for another try at Haskell
Not sure if it became much better in the past few years; I seem to run into issues less and less often (and all were solvable rather quickly), but it may be simply because of the avoidance of problematic packages and approaches. Here is a bit more of information though:
- Cabal 2 supports Nix-style local builds [1].
- Stack seems to aim very smooth and easy workflow. Though it didn't work well for me when I tried it (I think I had a version that was too old, and was rather appalled by the way they suggest to install a new one, with `curl ... | sh`). And as mentioned above, I prefer the opposite of adding even more layers on top of cabal and using multiple package managers, in part because it introduces more ways for things to go wrong. Likewise with Nix outside of NixOS.
- I used to find Cabal sandboxes helpful, but not using them these days. Different versions of the same package can be installed simultaneously, if it becomes tricky to stick to a single version while using Cabal. And as the last resort one can wipe out the local package database.
- Careful versioning could solve some of the package management and dependency resolution issues, and there is a common package versioning policy [2] (similar to, but not exactly the same as semver), but not everybody follows it (in versioning their packages or in pinning dependencies); one should be careful with packages that don't.
- Minimizing dependencies can be useful for this, among other things. Without keeping an eye on it, one can easily find themselves in a dependency graph with hundreds of packages, often coming pretty much directly from developers (without additional package maintainers keeping an eye on them following any standards or being compatible, though it's supposed to be better with Stack), some of which may misbehave at some point.
- On some systems it is viable to use a system package manager for Haskell package management.
But possibly my experiences are not representative either: as mentioned before, opinions and approaches around it tend to vary.
Hmm, okay so Spock looks nice. Neat! The routing code looks very easy to read:
Alright, let's figure out how this works. Okay that makes sense. Takes in a port, some middleware and spits out IO. Not crazy. Hmm, okay these dollar signs are making things a little confusing, but I can figure out that spock returns an IO Middleware and therefore we can see that spock takes the output of the right do block along with spockCfg. Not the easiest to scan at first glance because of the right to left reading but okay.Let's look at get.
Ah, okay, so uh...this doesn't appear to be a function. Oh, I see, here it is: Well I'm not really sure what xs, ps or st are. And HVectElim doesn't tell me anything. Path is pretty clear and SpockCtxM is a monad of sorts. But what the hell is HVectElim? Alright whatever, let's just figure it out via the signatures of Spock.html/Spock.json. Which are...nowhere to be found in the documentation. I looked them up in the index and nope, no entries. I'm very confused about what HVectElim is and why that would be a fine name.I can continue past this point, but you get my gist.
This is actually way better than my other experiences with Haskell and web dev, but it's by no means an easy or fun experience reading this code. Variable names like xs, ps, etc. are great when you're dealing with a generic list but they get old when that's the only documentation (because "type signatures are documentation" is totally infallible). And a name like HVectElim is just baffling. What, did they charge you per letter? Did half your keys fall out? Why is that a good name?
I really want to use Haskell for something useful. But I'm running into pain points trying to understand a glorified Hello World. Now granted I'm by no means a great Haskell programmer. But that's kind of the point. I understand monads. I get functors and applicatives. I should be able to write a dumb Hello World app dammit.