I won't presume to speak for the Clojure norm, but nice solution - it seems idiomatic to me at least. I think I might still consider the macro approach but can't justify one over the other without some more naval gazing, particularly around composibility. On 15 Mar 2015 19:20, "Andrew Oberstar" <ajobers...@gmail.com> wrote:
> 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. > -- 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.