This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-04-13
Channels
- # admin-announcements (2)
- # beginners (27)
- # boot (85)
- # cider (24)
- # cljs-dev (20)
- # cljsrn (16)
- # clojure (73)
- # clojure-brasil (2)
- # clojure-czech (152)
- # clojure-dusseldorf (7)
- # clojure-france (3)
- # clojure-japan (1)
- # clojure-norway (1)
- # clojure-poland (7)
- # clojure-russia (140)
- # clojure-uk (7)
- # clojurescript (66)
- # cursive (20)
- # datomic (8)
- # emacs (7)
- # events (1)
- # hoplon (325)
- # jobs (2)
- # jobs-discuss (69)
- # leiningen (3)
- # off-topic (6)
- # om (48)
- # onyx (82)
- # parinfer (1)
- # planck (10)
- # re-frame (53)
- # reagent (8)
- # ring (103)
- # untangled (13)
- # yada (14)
@weavejester: I'm curious. Do you prefer to use a closure for that because the lifetime of the connection spans multiple requests? I'm trying to understand when to use that method vs attaching values to the request itself.
@zane it has a few advantages
The first is that it means you’re only destructuring or setting things up once.
So (fn [config] (let [x (expensive-operation config)] (fn [request] …)))
The second advantage is that it’s less complex. You’re not tying the configuration to your request map.
Ideally you should only be passing around the information you need.
Finally, closures fit in well with architecture like Stuart’s component library
I mean, the handler is going to have to get a database value at some point. And if I'm understanding Ring correctly the way to do that is through the request map.
The request map is just for requests. I don’t think you need to tie your database to your request map.
There’s no inherent connection between the information in HTTP request, and a database connection
The request map is really designed for data from the HTTP request, or data derived from the HTTP request, like sessions, cookies, etc.
Ring handlers are single-argument functions.
But you can use a closure
(fn [config] (fn [request] response))
In other words you write a function that takes some configuration, and returns a handler.
I think so. It means that the configuration (e.g. database connections, etc.) is separate from your request map.
It’s how I do it in Duct.
I also did a presentation that covers this topic: https://skillsmatter.com/skillscasts/7229-duct-covered
I think you need to sign up to see the video, but it’s free.
I don’t think components should be overused… in fact I’m more conservative than Stuart on that point.
Right. Dependency injection is kind of insidious in that it kind of wants to take over your whole program.
My rule of thumb is that components are for things that have a start and a stop.
So anything that needs to be initialised or cleaned up
Sometimes cleanup can be left to the GC
Anything else shouldn’t be a component IMO.
That's what I thought too, but I see you're using a component for the Ring handler in Duct, so I was confused.
That’s because they have a setup part to them.
Endpoints are essentially constructors for handlers.
They also have a cleanup - deleting the handler - but this is handled by the GC
My initial thought was that things that only had setup (and no teardown) would be simpler to represent just using functions, closures, records, etc.
Well, endpoints are functions, but they’re promoted into components in order to integrate with the system.
I think you want to minimise how many components you have, but if you have something with a setup, even if the cleanup is just to let the GC handle it, then often it makes sense to either wrap it in a component, or hang it off a component somehow.
Yeah, the "in order to integrate with the system" part is what I meant when I said that dependency injection wants to eat the world.
Often you can draw a hard line between the system and everything else.
It's like the IO monad in Haskell. Once one part of your program becomes componentized there's a strong pull to componentize everything else so it all plays well together.
For instance, an endpoint component goes down into an endpoint function.
I think it is a little like the IO monad, but the same principle applies; you should aim to minimise things the IO monad touches.
I tend to find there’s not much initial setup I need to do, outside of IO, threads and things that seem a natural fit for components.
So, yeah. It's interesting to me that you drew the line after the handler / middleware stuff. I was going to draw it before.
I’d draw the line at the endpoints.
So the endpoints represent moving from the system into a more functional realm.
I also make use of what I’ve taken to calling “boundary protocols"
So if I want to interact with a component, I’ll do so through a protocol
That gives me a clean border between the messy outside world of components and IO
And my cleaner functional world inside.
It’s still better to have pure functions
But accessing a component through a protocol is better than accessing it directly
Not least because that makes it much easier to test.
When you say "through a protocol" I'm assuming you don't mean through the component protocol itself. You mean through a more abstract protocol that hides the underlying componentness of the implementation?
Right
So if I have a database component
Then I would wrap it in a protocol that abstracts the access
Allowing me to substitute a mock using something like Shrubbery https://github.com/bguthrie/shrubbery
The other advantage of it is that it forces me to define a clear boundary.
So I know that the database component will only be accessed in these ways.
It’s not as good as a pure function, but it’s better than nothing.
I tend to view complexity as something that needs to first be destroyed, and if it can’t be destroyed, it should be contained. So immutable data structures and pure functions first, and then some form of encapsulation if you absolutely have to access some form of I/O or side-effects.
Yeah, I mean. I like to think that one of the nice things about the functional approach in general and Clojure in particular is that you don't generally need things like stubs and mocks.
Right. Though I think mocks and stubs still have their place, they’re just much less common.
One of the problems with OOP is that it overuses niche tools, like inheritance and encapsulation. They have their place, but most of the time they’re not the right tool.
I’ve had opportunity to use it once
I’m currently using it in a game where entities have common behaviour
I’m using my Intentions library https://github.com/weavejester/intentions
But it’s pretty niche. Maybe inheritance has a purpose in validations? Not sure.
Hmm. That felt better than pulling the shared behavior out into a function and just passing it to the other functions that needed it?
Right, that’s the way I’d normally do it… but the way I have it set up happens to favour inheritance.
To give some more background, I have a Datomic-like database, Ittyon, that works based around behaviour of aspects keywords.
So an aspect like, I don’t know, :player/health, has certain behaviours associated with it.
That naturally makes one think about multimethods, right?
But with Intentions and inheritance, combining behaviour (which is common) is easier than trying to call multimethods with different keys.
Because aspects have a lot of behaviour in common, the inheritance model happens to make a lot of sense.
Especially if you have a way of combining inherited behaviour automatically.
Not sure if that makes sense? I’d explain it more but that would mean explaining more about Ittyon.
It’s a really niche use, though
And I’ve never had opportunity to use it before.
There are a few things in Clojure that seem pretty niche, until you need to use them.
I don’t think I’ve used refs or agents in a real program yet