4 ms·
Let's walk through my thoughts. Hmm, okay so Spock looks nice. Neat! The routing code looks very easy to read: main = do spockCfg <- defaultSpockCfg ()
by _hardwaregeek 7y ago
Let's walk through my thoughts.
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.
- defanor 7y agoThat 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.
- _hardwaregeek 7y agoBasic 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?) [1]: https://www.spock.li/tutorials/ https://www.spock.li/tutorials/ [2]: https://api.rocket.rs/v0.4/rocket/ https://api.rocket.rs/v0.4/rocket/