I was able to do this without a macro. Here's what I have now: (defprotocol IdempotentLifecycle (-started? [this]) (-start [this]) (-stop [this]))
(defn extend-lifecycle [atype] (extend atype component/Lifecycle {:start (fn [this] (if (-started? this) this (-start this))) :stop (fn [this] (if (-started? this) (-stop this) this))})) I had to use extend and not one of the extend-type or extend-protocol macros. Haven't dug into why yet. The usage for this would now be: (defrecord SillyExample [started] IdempotentLifecycle (-started? [this] (:started this)) (-start [this] (update this :started not)) (-stop [this] (update this :started not))) (extend-lifecycle SillyExample) So it seems like this approach results in implementation sharing without implementation inheritance. Is that the more general Clojure approach to reach for when an OO-programmer would usually reach for an abstract class? Andrew Oberstar On Sun, Mar 15, 2015 at 1:36 PM Andrew Oberstar <ajobers...@gmail.com> wrote: > Sure, that makes sense. I'll give that a go along with a few other ideas, > and see what works out best. Thanks for the help! > > > Andrew Oberstar > > On Sun, Mar 15, 2015 at 1:22 PM Colin Yates <colin.ya...@gmail.com> wrote: > >> I don't have one at hand (as I literally wrote my first macro last >> week ;)) but the way it could work is something like: >> >> (defmacro idempotent-component >> [{:keys [name start started? stop]}] >> `(defrecord (symbol name) [...] >> component/Lifecycle >> (start [this#] >> (if (~'started? this#) >> this# >> (~' this))) >> (stop [this#] >> (if (~'started? this#) >> (~`stop this#) >> this))) >> >> and called like >> >> (idempotent-component {:name "SillyExample" start: println started?: >> (constantly true) stop: println}) >> >> There are probably a 100 things wrong with that macro and possiblly >> even the idea of using a macro here, but hopefully it opens up the >> possibilities. >> >> On 15 March 2015 at 17:40, Andrew Oberstar <ajobers...@gmail.com> wrote: >> > Thanks, Colin. Macros hadn't crossed my mind, so it's good to have them >> > pointed out. Do you have a macro you could post that is a good example >> of >> > enforcing a pattern as an implementation detail? I think that's a good >> > general consideration, but I don't believe it fits this use case. >> Though an >> > example may be more illuminating. >> > >> > In some ways, this use case seems akin to the implementation of nth in >> > ClojureScript, where it's exact behavior can differ depending on the >> > protocols satisfied by the passed collection. >> > >> > Looking back through the component docs, update-system and >> > update-system-reverse may be the key piece for implementing something >> like >> > the LifecycleStatus solution in my original email without requiring any >> > change to component itself. The big weakness is that it would require >> using >> > a custom start-system stop-system function rather than the standard one. >> > >> > Andrew Oberstar >> > >> > On Sun, Mar 15, 2015 at 11:32 AM Colin Yates <colin.ya...@gmail.com> >> wrote: >> >> >> >> In OO we tend to solve the 'copy and paste' problem with abstract >> >> classes. In Clojure we also have macros, easily overused, sure, but >> >> worth knowing about. They turn the problem on its head and allow truly >> >> composable functionality. I am not stating they _are_ appropriate >> >> here, only that you might want to think about them; whenever I have a >> >> 'I want this pattern enforced, but it is really just an implementation >> >> detail', a macro is sometimes the answer. >> >> >> >> On 15 March 2015 at 15:58, Andrew Oberstar <ajobers...@gmail.com> >> wrote: >> >> > I'm fairly new to Clojure, so I'm still struggling to unlearn the >> habits >> >> > of >> >> > OO-programming. While using Stuart Sierra's component library, I've >> >> > found >> >> > the recommendation in the docs of using idempotent lifecycles very >> >> > helpful. >> >> > The unfortunate result is that every component then has the same >> pattern >> >> > in >> >> > its start and stop methods: >> >> > >> >> > (defrecord SillyExample [...] >> >> > component/Lifecycle >> >> > (start [this] >> >> > (if (custom-started-check? this) >> >> > this >> >> > (custom-start-logic this))) >> >> > (stop [this] >> >> > (if (custom-started-check? this) >> >> > (custom-stop-logic this) >> >> > this))) >> >> > >> >> > It adds some extra nesting and, potentially, duplication of the >> started >> >> > check's logic. In hopes of making idempotent lifecycles easier to >> >> > implement, >> >> > I made the following protocol, which seems to violate the >> implementation >> >> > inheritance philosophy of Clojure. >> >> > >> >> > (defprotocol IdempotentLifecycle >> >> > (started? [this]) >> >> > (safe-start [this]) >> >> > (safe-stop [this])) >> >> > >> >> > (extend-protocol component/Lifecycle >> >> > my.ns.IdempotentLifecycle >> >> > (start [this] >> >> > (if (started? this) >> >> > this >> >> > (safe-start this))) >> >> > (stop [this] >> >> > (if (started? this) >> >> > (safe-stop this) >> >> > this))) >> >> > >> >> > So then a use case would like more like: >> >> > >> >> > (defrecord SillyExample [...] >> >> > IdempotentLifecycle >> >> > (started? [this] >> >> > (custom-started-check this)) >> >> > (safe-start [this] >> >> > (custom-start-logic this)) >> >> > (safe-stop [this] >> >> > (custom-stop-logic this))) >> >> > >> >> > This seems like an easier end-user experience, but it feels wrong to >> >> > implement a protocol with another protocol. A more "Clojurey" feeling >> >> > option >> >> > would require changes to the component library itself: >> >> > >> >> > (defprotocol LifecycleStatus >> >> > (started? [this])) >> >> > >> >> > (extend-protocol LifecycleStatus >> >> > java.lang.Object >> >> > (started? [_] false)) >> >> > >> >> > ;; Lifecycle protocol stays as-is >> >> > >> >> > (defn safe-start [component] >> >> > (if (started? component) >> >> > this >> >> > (start component))) >> >> > >> >> > (defn safe-stop [component] >> >> > (if (started? component) >> >> > (stop component) >> >> > this)) >> >> > >> >> > Then component would need to use safe-start/safe-stop in place of >> >> > regular >> >> > start/stop in the start-system/stop-system functions. >> >> > >> >> > Maybe this is better suited to an issue/pr on his repository, but I >> >> > wanted >> >> > to see if there were any comments from the community. Is there a >> better >> >> > way >> >> > to do this? >> >> > >> >> > Andrew Oberstar >> >> > >> >> > -- >> >> > You received this message because you are subscribed to the Google >> >> > Groups "Clojure" group. >> >> > To post to this group, send email to clojure@googlegroups.com >> >> > Note that posts from new members are moderated - please be patient >> with >> >> > your >> >> > first post. >> >> > To unsubscribe from this group, send email to >> >> > clojure+unsubscr...@googlegroups.com >> >> > For more options, visit this group at >> >> > http://groups.google.com/group/clojure?hl=en >> >> > --- >> >> > You received this message because you are subscribed to the Google >> >> > Groups >> >> > "Clojure" group. >> >> > To unsubscribe from this group and stop receiving emails from it, >> send >> >> > an >> >> > email to clojure+unsubscr...@googlegroups.com. >> >> > For more options, visit https://groups.google.com/d/optout. >> >> >> >> -- >> >> You received this message because you are subscribed to the Google >> >> Groups "Clojure" group. >> >> To post to this group, send email to clojure@googlegroups.com >> >> Note that posts from new members are moderated - please be patient with >> >> your first post. >> >> To unsubscribe from this group, send email to >> >> clojure+unsubscr...@googlegroups.com >> >> For more options, visit this group at >> >> http://groups.google.com/group/clojure?hl=en >> >> --- >> >> You received this message because you are subscribed to the Google >> Groups >> >> "Clojure" group. >> >> To unsubscribe from this group and stop receiving emails from it, send >> an >> >> email to clojure+unsubscr...@googlegroups.com. >> >> For more options, visit https://groups.google.com/d/optout. >> > >> > -- >> > You received this message because you are subscribed to the Google >> > Groups "Clojure" group. >> > To post to this group, send email to clojure@googlegroups.com >> > Note that posts from new members are moderated - please be patient with >> your >> > first post. >> > To unsubscribe from this group, send email to >> > clojure+unsubscr...@googlegroups.com >> > For more options, visit this group at >> > http://groups.google.com/group/clojure?hl=en >> > --- >> > You received this message because you are subscribed to the Google >> Groups >> > "Clojure" group. >> > To unsubscribe from this group and stop receiving emails from it, send >> an >> > email to clojure+unsubscr...@googlegroups.com. >> > For more options, visit https://groups.google.com/d/optout. >> >> -- >> You received this message because you are subscribed to the Google >> Groups "Clojure" group. >> To post to this group, send email to clojure@googlegroups.com >> Note that posts from new members are moderated - please be patient with >> your first post. >> To unsubscribe from this group, send email to >> clojure+unsubscr...@googlegroups.com >> For more options, visit this group at >> http://groups.google.com/group/clojure?hl=en >> --- >> You received this message because you are subscribed to the Google Groups >> "Clojure" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to clojure+unsubscr...@googlegroups.com. >> For more options, visit https://groups.google.com/d/optout. >> > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.