Last time I discussed using a hash maps to improve code reuse with protocols. While we produced a set of good solutions, I couldn't help but feel like we aren't quite there. There still is a fair amount of chatter on the list about how to implement a default protocol, for example.
I started experimenting with an idea I'm calling a Partially Implemented Protocol (PIP). The idea is that you can use a PIP like you would a BaseClass in Java. After playing around for a little bit, I settled on the following solution:
The two major components is the PIP record and extend+ macro. Let me re-write yesterday's example with the new expand+ syntax.
(defprotocol UIProtocol
 (render [])
 (action [evt])
 (button-down [evt])
 (button-up [evt]))
(def BaseUIProtocol
 (pip UIProtocol
 {:action (fn [this evt] ...)
   :button-down (fn ([this evt]...)
   :button-up (fn ([this evt]...)})
(extend+ TerranUIType
  BaseUIProtocol
  {:render an-existing-fn})
(extend+ ProtossUIType
  BaseUIProtocol
  {:render a-different-fn})
What I really like about the use of a PIP is that it allows reference to the protocol & partial implementation with one record. You can get at the internal simply by using keyword lookup, and you can also derive a PIP from another PIP like so:
(def up-down-override
  :button-down button-down-2
  :button-up button-up-2})
(extend+ ZergUIType
  (pip BaseUIProtocol up-down-override)
  {:render a-buggy-fn})
I'm also working on versions of reify+ proxy+, etc. that all work with PIPs, assuming this idea is worth exploring.
Thursday, August 12, 2010
Wednesday, August 11, 2010
Protocol Implementation Awesomeness
I recently read a post about Racket & Clojure, and there was a question about protocols raised. I wanted to take the time to discuss composition of implementations with Protocols. Of course, you probably know this term better as inheritance.
Personally, my main driver for multiple inhertiance is being able to re-use different partial implementations of a class. There are a lot a times I would want to mix & match implementations of this protocol, such as UI code. Consider the following protocol
(defprotocol UIProtocol
 (render [])
 (action [evt])
 (button-down [evt])
 (button-up [evt]))
Now, we can extend the protocol as such
(extend UIType
  UIProtocol
  {:render an-existing-fn
   :action (fn [this evt] ...)
   :button-down (fn ([this evt]...)
   :button-up (fn ([this evt]...)})
So far so good. However, suppose we have another UI type we want to define
(extend AnotherUIType
  UIProtocol
  {:render a-different-fn
   :action (fn [this evt] ...)
   :button-down (fn ([this evt]...)
   :button-up (fn ([this evt]...)})
The only thing that is different between the implementations is the rendering logic (similar behavior, different skin? What sort of problem domain has that?). Our implementation works, but it's repetitive.
In order to clean it up, we'll need to use a defining feature of Lisp: code is data. Remember we are building a data structure to define our type. As such, we can use every DRY trick we know. In our specific example, we take advantage of the fact that the extend macro is expecting a hash-map, and it doesn't care how it gets there. So, here's a new take on the code.
(def base-impl
  {:action (fn [this evt] ...)
   :button-down (fn ([this evt]...)
   :button-up (fn ([this evt]...)})
(extend TerranUIType
  UIProtocol
  (merge base-impl {:render an-existing-fn}))
(extend ProtossUIType
  UIProtocol
  (merge base-impl {:render a-different-fn})
BAM! I've overridden render with my specific implementation. We just simulated the abstract base class pattern. Now, let's see how flexible this really lets us be.
(def up-down-override
  :button-down button-down-2
   :button-up button-up-2})
(extend ZergUIType
  UIProtocol
  (merge base-impl up-down-override {:render a-buggy-fn});-p
What this let me do is define my own inheritance rules a la carte. No implicit order of operations, no compiler limitations, no confusing precedence rules. If you can figure out the map merging, you can do it. Period. That's why I think Clojure Protocols are so awesome!
Personally, my main driver for multiple inhertiance is being able to re-use different partial implementations of a class. There are a lot a times I would want to mix & match implementations of this protocol, such as UI code. Consider the following protocol
(defprotocol UIProtocol
 (render [])
 (action [evt])
 (button-down [evt])
 (button-up [evt]))
Now, we can extend the protocol as such
(extend UIType
  UIProtocol
  {:render an-existing-fn
   :action (fn [this evt] ...)
   :button-down (fn ([this evt]...)
   :button-up (fn ([this evt]...)})
So far so good. However, suppose we have another UI type we want to define
(extend AnotherUIType
  UIProtocol
  {:render a-different-fn
   :action (fn [this evt] ...)
   :button-down (fn ([this evt]...)
   :button-up (fn ([this evt]...)})
The only thing that is different between the implementations is the rendering logic (similar behavior, different skin? What sort of problem domain has that?). Our implementation works, but it's repetitive.
In order to clean it up, we'll need to use a defining feature of Lisp: code is data. Remember we are building a data structure to define our type. As such, we can use every DRY trick we know. In our specific example, we take advantage of the fact that the extend macro is expecting a hash-map, and it doesn't care how it gets there. So, here's a new take on the code.
(def base-impl
  {:action (fn [this evt] ...)
   :button-down (fn ([this evt]...)
   :button-up (fn ([this evt]...)})
(extend TerranUIType
  UIProtocol
  (merge base-impl {:render an-existing-fn}))
(extend ProtossUIType
  UIProtocol
  (merge base-impl {:render a-different-fn})
BAM! I've overridden render with my specific implementation. We just simulated the abstract base class pattern. Now, let's see how flexible this really lets us be.
(def up-down-override
  :button-down button-down-2
   :button-up button-up-2})
(extend ZergUIType
  UIProtocol
  (merge base-impl up-down-override {:render a-buggy-fn});-p
What this let me do is define my own inheritance rules a la carte. No implicit order of operations, no compiler limitations, no confusing precedence rules. If you can figure out the map merging, you can do it. Period. That's why I think Clojure Protocols are so awesome!
Thursday, April 8, 2010
Steve Jobs just ruined the iPhone for Clojure
Recently Apple released new terms of service with their iPhone OS. By now most of you have seen the following section. I've copied this from Daring Fireball
If I'm reading the TOS, we can't have a version of Clojure on this platform. Any ideas I have - no, we have - now can't be shared with the world.
This is NOT thinking differently Apple.
3.3.1 — Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs. Applications must be originally written in Objective-C, C, C++, or JavaScript as executed by the iPhone OS WebKit engine, and only code written in C, C++, and Objective-C may compile and directly link against the Documented APIs (e.g., Applications that link to Documented APIs through an intermediary translation or compatibility layer or tool are prohibited).I'm pissed off. I believe the hype, and there's a lot of HCI experiments I'd like to do with the iPhone & iPad. There's a ton of opportunities & ideas, waiting to be discovered. Being able to do these experiments in Clojure would be some much fun, that it's worth the $500 investment.
If I'm reading the TOS, we can't have a version of Clojure on this platform. Any ideas I have - no, we have - now can't be shared with the world.
This is NOT thinking differently Apple.
Tuesday, April 6, 2010
Friday, March 26, 2010
Too much Clojure when...
I can no long spell the following words properly. This is really annoying when talking to people outside of this community :)
What're your experiences?
- Closure/Clojure
- Definitely/Defnitely
- Disclosure/Disclojure (self inflicted)
- Composure/Compojure
What're your experiences?
Thursday, February 18, 2010
Thoughts on namespace management
Konrad Hinsen recently posted some ideas for handling namespaces
http://onclojure.com/2010/02/17/managing-namespaces
There are a lot of good ideas in there, and right now I'd like to talk specifically about the proposed :like & :clone clauses. I have a bit of a different approach.
Supposed we add a new project file, namespace_config.clj. It would be a basic rules engine for configuring namespaces. I'm thinking there would be a macro, extend-ns, that is roughly defined as follows. The exact implementation needs work.
This lets us set up a series of predicates & resulting actions that would be applied to each ns in the project. Everything would be done in one central location, so (hopefully) that would cut down on maintenance.
http://onclojure.com/2010/02/17/managing-namespaces
There are a lot of good ideas in there, and right now I'd like to talk specifically about the proposed :like & :clone clauses. I have a bit of a different approach.
Supposed we add a new project file, namespace_config.clj. It would be a basic rules engine for configuring namespaces. I'm thinking there would be a macro, extend-ns, that is roughly defined as follows. The exact implementation needs work.
This lets us set up a series of predicates & resulting actions that would be applied to each ns in the project. Everything would be done in one central location, so (hopefully) that would cut down on maintenance.
Sunday, January 24, 2010
Code Kata: A data sifter
Here's a problem inspired by a scheme example I discovered another day.
http://programming-musings.org/2006/02/07/scheme-code-kata/
Write a data sifter, sift, that partitions a string into a list of lists. Start with the case of using letters as a delimiter, and numbers as data. There can be any number of repetitions of numbers & letters.
user=>(sift "a1b2cd34")
(("a" ("1")) ("b" ("2")) ("c" ()) ("d" ("3" "4")))
Next, add the ability to your sift function to accept a list as input, as well as a string.
http://programming-musings.org/2006/02/07/scheme-code-kata/
Write a data sifter, sift, that partitions a string into a list of lists. Start with the case of using letters as a delimiter, and numbers as data. There can be any number of repetitions of numbers & letters.
user=>(sift "a1b2cd34")
(("a" ("1")) ("b" ("2")) ("c" ()) ("d" ("3" "4")))
Next, add the ability to your sift function to accept a list as input, as well as a string.
user=>(sift ("a" "1" "b" "2" "c" "d" "3" "4"))
(("a" ("1")) ("b" ("2")) ("c" ()) ("d" ("3" "4")))
After that, add the ability to take a vector/array as an input
user=>(sift ["a" "1" "b" "2" "c" "d" "3" "4"])
(("a" ("1")) ("b" ("2")) ("c" ()) ("d" ("3" "4")))
Finally, let your sift accept a collection of any object, and an arbitrary predicate. If the predicate is true, the object is a delimiter (e.g. a String). If the predicate is false, the object is data (e.g. a Number).
user=>(sift string? ["a" 1 "b" 2 "c" "d" 3 4])
(("a" (1)) ("b" (2)) ("c" ()) ("d" (3 4)))
I'll be posting my solution in about a week.
Sunday, January 3, 2010
1.2 fn Proposal: same & multisame
Hello Clojure Developers,
Writing software frequently follows the same process. Observing & understanding the processes and coming up with effective solutions is the task of library design. An API is judged by how well it fits into the process.
Application design is a slightly different process. For the purposes of this proposal, it involves three oversimplified steps.
However, there currently is not much work done to bring steps 2 & 3 closer to each other. This is evidenced by the fact that there are specialized namespaces in contrib for handling strings (str-utils2), functor application (generic.functor), and I was in the middle of proposing additions to the map-utils library.
All this code duplication started to smell. Here we all are writing specialized routines to AVOID using the sequence functions in our code. This is not right.
I have a proposal to eliminate this smell. I've written a higher order function called same. Here's the doc:
lib.sfd.same/same
([index? seq-fn & args])
"same is a mutlimethod that is designed to "undo" seq. It expects a seq-fn that returns a normal seq, and the appropraite args. By default it converts the resulting seq into the same type as the last argument. An optional leading integer, index, can be provided to specify the index of the argument that should be used to convert the seq. If it is a sorted seq, the comparator is preserved.
This operation is fundamentally eager, unless a lazy seq is detected. In this case no conversion is attempted, and laziness is preserved."
Please take a moment to review a fairly robust list of examples now:
http://github.com/francoisdevlin/devlinsf-clojure-utils/blob/master/test/lib/sfd/same_test.clj
Afterwards, you can peruse the code here:
http://github.com/francoisdevlin/devlinsf-clojure-utils/blob/master/src/lib/sfd/same.clj
This one function will provide the same functionality as the proposed map-utils, some of c.c.str-utils2, c.c.generic.functor, or any desired set & vector utils. It's based on a multimethod, so you are a simple defmethod addition away from keyword-utils or symbol-utils (assuming you'd want to treat them like strings).
I've also designed a method, multi-same, for functions that take a sequence in and split it into several sequences. Here's a quick example, as the uses for multi-same are still being developed.
user=>(multi-same partition 2 "abcd")
("ab" "cd")
One thing that I find VERY fascinating is the areas where same & multi-same do NOT allow str-utils2 to be replaced out of the box. Some of these can easily be explained. str-utils2/trim is a very string specific piece of code. However, others cannot easily be explained. Why is it that there is no way to split a sequence similar to a regular expression?
I think these areas where string processing is easier represent places we need to improve our sequence library. I've included some new functions in lib.sfd.seq-utils, and I would ask this group to consider adding them to c.c.seq-utils or core.
There also isn't a parser that works with predicates & sequences in core yet. I suspect fn-parse may be a start. I'd appreciate help from anyone that is good with parsers/monads.
So, here's a chance to simultaneously reduce the amount of code in contrib and add lots of functionality to Clojure. In summary, here's what I'm proposing
UPDATE 1/3: I re-wrote same & multi-same to work with a protocol, per Stuart Sierra's suggestion.
I look forward to the discussion,
Sean
Writing software frequently follows the same process. Observing & understanding the processes and coming up with effective solutions is the task of library design. An API is judged by how well it fits into the process.
Application design is a slightly different process. For the purposes of this proposal, it involves three oversimplified steps.
- Convert problem data to a form the API can understand.
- Use the API to come up with a solved version of the problem.
- Convert the API produced solution back to the problem domain solution.
However, there currently is not much work done to bring steps 2 & 3 closer to each other. This is evidenced by the fact that there are specialized namespaces in contrib for handling strings (str-utils2), functor application (generic.functor), and I was in the middle of proposing additions to the map-utils library.
All this code duplication started to smell. Here we all are writing specialized routines to AVOID using the sequence functions in our code. This is not right.
I have a proposal to eliminate this smell. I've written a higher order function called same. Here's the doc:
lib.sfd.same/same
([index? seq-fn & args])
"same is a mutlimethod that is designed to "undo" seq. It expects a seq-fn that returns a normal seq, and the appropraite args. By default it converts the resulting seq into the same type as the last argument. An optional leading integer, index, can be provided to specify the index of the argument that should be used to convert the seq. If it is a sorted seq, the comparator is preserved.
This operation is fundamentally eager, unless a lazy seq is detected. In this case no conversion is attempted, and laziness is preserved."
Please take a moment to review a fairly robust list of examples now:
http://github.com/francoisdevlin/devlinsf-clojure-utils/blob/master/test/lib/sfd/same_test.clj
Afterwards, you can peruse the code here:
http://github.com/francoisdevlin/devlinsf-clojure-utils/blob/master/src/lib/sfd/same.clj
This one function will provide the same functionality as the proposed map-utils, some of c.c.str-utils2, c.c.generic.functor, or any desired set & vector utils. It's based on a multimethod, so you are a simple defmethod addition away from keyword-utils or symbol-utils (assuming you'd want to treat them like strings).
I've also designed a method, multi-same, for functions that take a sequence in and split it into several sequences. Here's a quick example, as the uses for multi-same are still being developed.
user=>(multi-same partition 2 "abcd")
("ab" "cd")
One thing that I find VERY fascinating is the areas where same & multi-same do NOT allow str-utils2 to be replaced out of the box. Some of these can easily be explained. str-utils2/trim is a very string specific piece of code. However, others cannot easily be explained. Why is it that there is no way to split a sequence similar to a regular expression?
I think these areas where string processing is easier represent places we need to improve our sequence library. I've included some new functions in lib.sfd.seq-utils, and I would ask this group to consider adding them to c.c.seq-utils or core.
There also isn't a parser that works with predicates & sequences in core yet. I suspect fn-parse may be a start. I'd appreciate help from anyone that is good with parsers/monads.
So, here's a chance to simultaneously reduce the amount of code in contrib and add lots of functionality to Clojure. In summary, here's what I'm proposing
- Add same to core
- Add multi-same to core
- Add new sequence fns to contrib or core
- Add a new sequence parser to contrib or core
UPDATE 1/3: I re-wrote same & multi-same to work with a protocol, per Stuart Sierra's suggestion.
I look forward to the discussion,
Sean
Subscribe to:
Posts (Atom)