45 ms·
At the risk of proving your point (this part of the code is indeed a bit of a hash), but in the hope that this will help a little with learning, here are some a
by phlakaton 7y ago
At the risk of proving your point (this part of the code is indeed a bit of a hash), but in the hope that this will help a little with learning, here are some answers:
1. The first do in your snippet does nothing - it's redundant. In the actual code you linked, though, it sequences the `get "/" ...` and the `get "/:short" ...` expressions into a single application initializer. When the app starts up (i.e. when `app` is called), this outer "do" runs through your endpoint-defining expressions and registers each endpoint in turn with the HTTP server.
2. That `get` function takes two arguments: the URI pattern and the action to run if the pattern matches. The whole rest of your snippet _is_ the second argument! Because of the low precedence of the "$" operator, using "$" means you don't have to enclose all that code in parentheses. It is a tiny bit of syntactic sugar that is wildly popular in Haskell-land because Heaven forfend any of your code ends up looking like LISP. ;-)
3. The answer is in the monad being used in this case to sequence these steps together (`ActionT Text IO <some return type>`). Think of `param "uri"` not as an expression returning a string, but a partially-constructed function that takes an HTTP request as an additional argument. The monad takes care of threading the request into this function when the request is actually handled, and `uri` will be bound to the resulting value.
4. I'm guessing it doesn't. Many type signatures in Haskell are optional, and are added if either 1) there's a genuine ambiguity in the code that needs to be clarified, 2) the author thinks it will make things clearer, or 3) the author hit a bug where the compiler inferred some crazy generic type that the author didn't intend or understand, and they put in some type signatures as sanity checks and constraints on what type is actually expected.
- microtherion 7y agoThanks for your explanations! 2&4 make a lot of sense. 1, and even more so 3, lead to more questions than they answer, in my opinion. Monads appear to be an absolutely indispensible aspect of most substantial Haskell programs, but to me, they just look incomprehensible. I understand the advantages of a pure programming model for formal reasoning about programs, but in terms of communicating an intent with a program, I can't help thinking that the cure offered by Monads is far worse than the disease of mutable state.
- rovolo 7y ago1) I'm guessing that the point of confusion is 'does nothing - it's redundant'. This 'do block' is combining multiple ScottyMs together into one ScottyM. The 'Monad' interface means that ScottM tells you how to combine them together. You could write this in Java as interface ScottM { ScottM plusRoute(ScottM next) } get("/", ... ) .plusRoute(get("/:short", ...)) .plusRoute(...) The difference is that there's lots of library functions which can work on a Monad, but you'll have to write/import all of the utility methods to combine your custom RequestHandler interface. 3) > How is that request passed into "app"? "app" doesn't execute handle the request, it is a factory method returning a request-handler of type 'ScottM'. You hook in the 'app' to the webserver on line 94: scotty 3000 (app rConn) You might be getting confused by the "R.Connection" argument; that's the connection to the database, not the HTTP request. > How is it passed into the "get"? scotty will apply the "get" to the HTTP request encapsulated as an IO monad. I'm not sure what the internals of 'scotty' are like, but you're basically calling 'ScottM.handle(IOStream)'. 'get' is a 'ScottM' request handler which you combined with the other route handler. > How is it passed to "param"? "param" returns "ActionM", which is basically a "Consumer<IOStream>". The rest of the lines here are also "ActionM" which get combined together. The 'get' will in effect call "handle(IOStream)". Really understanding the niceties of this would require playing around with IO in haskell for a bit. The 'do' syntax is somewhat incomprehensible when you first see it because it is syntactic sugar for a pattern which is used in other languages, but which they don't name. You might have more success learning what it's doing by getting rid of the syntactic sugar and renaming (>>) and (>>=) to more familiar stuff from Java. Maybe it would be best to try it out using Java's CompletableFuture or Stream. https://en.wikibooks.org/wiki/Haskell/do_notation https://en.wikibooks.org/wiki/Haskell/do_notation
- phlakaton 7y agoThe advantage of monads is that they whisk away a bunch of boring, repetitive boilerplate code that nobody wants to write and that people will even go so far in other languages as to create a bunch of global/thread-local/hidden-context variables to avoid writing! The downside is that the abstraction and its applications, maybe a bit like pointers, do take a while to really master. Particularly when you start composing monad types to make other monads, as Scotty does with IO.