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.

Reply via email to