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.