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.

Reply via email to