4 ms·
It's more about controlling state. I like to think of ring[0] as a really nice example of this that's pretty easy to reason about. It abstracts http handling i
by Folcon 8y ago
It's more about controlling state. I like to think of ring[0] as a really nice example of this that's pretty easy to reason about.
It abstracts http handling into a series of key/value data structures. The request that's pulled in looks like this:
{:remote-addr "localhost"
:headers {"host" "localhost"
"content-type" "application/json"
"accept" "application/json"}
:server-port 80
:content-type "application/json"
:uri "/"
:server-name "localhost"
:query-string nil
:body ""
:scheme :http
:request-method :get}
The response like this:
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"}
Functions manipulate these structures, but their input and outputs are primarily those data structures.
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"})
Your app is now just a function stack that get's called in this order:
(def app
(-> handler
(wrap-content-type "text/html")))
Request => wrap-content-type => handler (generate response) => wrap-content-type => Returned
Want to add middleware? Define a function that handles it, here's an example[1]:
(defn middleware [handler]
(fn [request]
;; Do something to the request before sending it down the chain.
(let [response (handler request)]
;; Do something to the response that's coming back up the chain.
response)))
Then add the function to your app:
(def app
(-> handler
(wrap-content-type "text/html")
(wrap-keyword-params)
(wrap-params)))
You want to handle session data? Ring provides wrap session[2], add it and then use your own middleware or handler to manipulate the session by just changing the value at :session.
Example [3]:
(defn handler [{session :session}]
(let [count (:count session 0)
session (assoc session :count (inc count))]
(-> (response (str "You accessed this page " count " times."))
(assoc :session session))))
Much of your world is now reduced down to:
* Does this data structure look correct?
* How can I fix it?
* Do I need some resource like a db connection? Ok how do I add it to my data structure?
It all composes, and you end up worrying a lot less.
Finally I find that doing things this way ends up creating programs that just have much less explicit state, as opposed to temporary state that's derived from other values and so only changes when those things change.
You can program in other languages like this, it's just a bit harder, and you have to hope that no one else you're working with breaks one of the assumptions that a system like this is built on, as opposed to having the language enforce those for you.
Sorry if I've been unclear =)...
[0] - https://github.com/ring-clojure/ring/wiki https://github.com/ring-clojure/ring/wiki (Examples taken from ring).
[1] - https://stackoverflow.com/a/19459508 https://stackoverflow.com/a/19459508
[2] - https://github.com/ring-clojure/ring/blob/95e4ca25d5b98c45f927b32b5c3c85c21c999d96/ring-core/src/ring/middleware/session.clj#L77 https://github.com/ring-clojure/ring/blob/95e4ca25d5b98c45f9...
[3] - https://github.com/ring-clojure/ring/wiki/Sessions https://github.com/ring-clojure/ring/wiki/Sessions
- lugg 8y agoThis is the thing that is drawing me in. The fact that others really need babysitting. I've never had huge problems with writing programs php or JS. Now everyone is freaking out about typescript and PHP type hints and all I'm doing is sitting here scratching my head asking but why? What does it do for you? It looks like people were doing a lot of dirty state mutation and a lot of optional parameters that sometimes had mixed types because "then you can use it with an ID, an email, or a user object you silly billy". Both of those things create a god awful hell. The second example may sound easy to use but then you have now way to reason about code, you can no longer clearly compose pieces and variable names become less descriptive. Types provide you with a simple pair of hand cuffs that prevent this issue. But they come at a cost. My issue is that people with shitty habits should probably just change their shitty habbits. Changing from a dynamic language that lets you take a lot of safe shortcuts to a static language because you abused those shortcuts is really the wrong way to go. Moving to something like clojure looks like a much cleaner solution to the problem. Still handcuffs, but at least youre forcing the right thing (pushing the state mutation to the edge, leaving the business logic clear and rational without all the type safety bloat.
- chessturk 8y agoThis is exactly my feeling about Typescript. As a Go! developer, I'm fully for strong types, even to the point of restriction, but only if they are enforced. I'm currently maintaining a massive spaghetti code TypeScript nightmare that escape-hatches with @ts-ignore and `any` about every other line. I'm not sure why they even chose to give up all of the imperative, dynamic power of JS and then not even adhere to the pre-processor's own rules. I think there's an argument for imperative and functional approaches, but I also think trying to change an imperative language into a functional one is a bad idea, and trying to shoe-horn in type safety into a language that doesn't have it is just foolish. I feel like languages come with trade-offs inherent in their design (Go! trades DRY for type safety, JS trades data validation/type safety for convenience), and if you don't like the trade-offs of the language for your project, then you are probably using the wrong tool for the job. However, when it comes to JS, you usually don't have a choice (unless you go with Dart or WebASM, which I admittedly know very little about). I suppose TS could have a place (I particularly understand libraries that attempt to improve their data contract using it), but then I wish it didn't provide such wide variety of ways to escape or ignore it's restrictions. I feel like they are fake guard rails and provide a false sense of security.