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 <[email protected]>
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 <[email protected]> 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 <[email protected]> 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 <[email protected]>
>> 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 <[email protected]>
>> 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 [email protected]
>> >> > Note that posts from new members are moderated - please be patient
>> with
>> >> > your
>> >> > first post.
>> >> > To unsubscribe from this group, send email to
>> >> > [email protected]
>> >> > 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 [email protected].
>> >> > 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 [email protected]
>> >> Note that posts from new members are moderated - please be patient with
>> >> your first post.
>> >> To unsubscribe from this group, send email to
>> >> [email protected]
>> >> 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 [email protected].
>> >> 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 [email protected]
>> > Note that posts from new members are moderated - please be patient with
>> your
>> > first post.
>> > To unsubscribe from this group, send email to
>> > [email protected]
>> > 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 [email protected].
>> > 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 [email protected]
>> Note that posts from new members are moderated - please be patient with
>> your first post.
>> To unsubscribe from this group, send email to
>> [email protected]
>> 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 [email protected].
>> 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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.