Made any Rectangles lately? Is it left:right:top:bottom:? or is it top:bottom:left:right? or top:left:bottom:right:? LayoutFrames are even funner. Any time you get these long multiple arguments message, you have candidates for these keyword lists where the order isn't really important.
I've only played with Lisp a little, but I remember one of the cool things you could do was list the keywords in any order you wanted. Something like (Point y: 1 x: 4) (it's been a while so forgive me if that's not exactly right). What would it take to have the same ease and flexibility in Smalltalk?
doesNotUnderstand: comes to the rescue one again! Anytime you mess with dnu:, want to build (and test) the facilities first, saving the hacking of dnu: till the end. There's a couple methods that come into play here, we'll go one by 1.
We need a way of determining whether a method signature is even a candidate for keyword rearrangement. To be so, each of its keywords must be unique. We can keywords to a symbol to get an Array of the different keywords. We could just send keywords asSet size = keywords size, but that seemed computationaly expensive, so I implemented:
SequenceableCollection>>hasUniqueElements 1 to: self size - 1 do: [:n | | candidate | candidate := self at: n. n + 1 to: self size do: [:m | (self at: m) = candidate ifTrue: [^false]]]. ^true
It's about 15x faster than the one liner.
The next part is being able to determine if two selectors are "isotropic". I think I can use that term, if not, I'm sure someone will correct me. Basically we want to be able to detect that x:y: and y:x: are the same selectors, but with different keyword ordering. We can extend any class, we want, so how about Symbol.
Symbol>>isotropicMatch: aSymbol "aSymbol is assumed to be multi and and keyword unique" | mine his | self size = aSymbol size ifFalse: [^false]. self last = $: ifFalse: [^false]. mine := self keywords. his := aSymbol keywords. SequenceableCollectionSorter sort: mine. SequenceableCollectionSorter sort: his. ^mine = his
We want this one to go fast too. We're going to be scanning ALL of a given receivers selectors, so we want it to resolve quickly. The first two lines of the method are quick and get rid of almost all match candidates. We only have to run the last 5 lines on rare cases, almost always on a match.
Having this code, we can make sure that #x:y: isotropicMatch; #y:x: returns true, and #x:a: isotropicMatch: #x:y: does not.
Hack it in
This could probably be factored better. You have to do some of the work in the existing Object>>doesNotUnderstand: method though, because you want to return from it. One of the cool things I noticed doing this is that you can override a method in <None> When you're done, you just unload <None> and everything goes back to normal. Basically, we want to extend the default doesNotUnderstand: message so that it first checks to see if it's just an order mangled version of something it could understand, and if so, do so. Bolting this code
aMessage arguments size > 1 ifTrue: [(self findIsotropicSelector: aMessage selector) ifNotNil: [:isoSelector | | argMap | argMap := Dictionary new. aMessage selector keywords with: aMessage arguments do: [:a :b | argMap at: a put: b]. ^self perform: isoSelector withArguments: (isoSelector keywords collect: [:each | argMap at: each])]].
to the front of Object>>doesNotUnderstand: and add this supporting method:
findIsotropicSelector: aSelector aSelector keywords hasUniqueElements ifFalse: [^nil]. ^self class allSelectors detect: [:each | each isotropicMatch: aSelector] ifNone: [nil]
and it should work. The helper method enumerates the receiver's selectors looking for an isotropic match. The preamble to dnu: uses it to to determine if there's an isotropic equivalent, and if so, reorders the arguments with a map, and resends.
This is kind of fun. (Point y: 3 x: 5) = (5 @ 3) returns true! Of course, Rectangle top: 4 bottom: 6 left: 1 right: 3 prints as "1@4 corner: 3@6". You don't have to stick to class side instance creation methods either. You can do (Array new: 1) put: 'hi' at: 1! I don't know, I think that's kind of cool.