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.

Reply via email to