Thursday, July 21, 2011

OSX Lion: First Day Thoughts

I downloaded and installed Lion yesterday. Mostly, I'm still acclimating before drawing judgements and throwing darts. But one thing stands out already.

There's this great story about how Steve Jobs convinced Bill Atkinson that rectangles with rounded corners were a good thing. The idea being that we as humans tend to prefer them and so would naturally prefer them in our UIs. The rest is history. Over the years, more and more of our UI elements get sanded off corners.

There is another pendulum that's been swinging along the Apple UI Design vector that annoys me. Color. Or lack thereof. It's no secret that Apple uses some of its frontline apps to feel out UI evolutions. Back in 2006, I remember discussing the latest iTunes version, and describing what I saw as an attempt on Apple's part to create "Lickable Motiff."

The first thing that keeps hitting me with the subtle changes made to Mail and Finder, are that they both seem to have been through another bleach cycle. Mails top button bar has had all hue rinsed from it. Finder's side bar is nearly colorless. I keep thinking "Those icons are just glyphs from some foreign language. Ignore them." Maybe I should just spend more time in Terminal and use good ol' ls.

I understand some of the influences that drive Apple designers to do this. It's easier to harmonize sets of icons if you use less colors. It helps deal with colorblindness issues. Etc.

On the other hand... I'd like to take the Apple UI designers on a walk around their campus. And point at things. And say "See? Color!" If we get rounded rectangles (problematic as they are to graphics libraries) because they show up in real life, why can't we have some color too?

Who knows, maybe everyone at Apple wears black turtlenecks now, and they all really see less color. Yeah, maybe that's it.

Thursday, July 14, 2011

Rope: an evolved Chain

One of the areas my Chain did not come out ahead was in the area of adding truly new keys to a Dictionary (as opposed to just updating what already was stored at a given key) or removing them. Both of these operations potentially involve #become: sends. While VisualWorks #become: is very fast, it's not as fast as some other operations. And not all Smalltalk implementations have a fast #become:. Here's the chart of how well Chain compared to IdentityDictionary for original key add/removal.


While I was on vacation, John Brant pinged me encouraging me to use a #changeClassTo: strategy instead of #become:. To do so, first I cloned the Chain class as a new class called Rope (and the Empty variant as well).

Then we make EmptyRope a subclass of Rope. This is important, because we need an EmptyRope to have the same instance variable layout as a Rope. #changeClassTo: will only change the type or class of an object, if they have the same instance variable layout. We'll just make sure that never actually set or reference the key, value, or nextKnot variables in the EmptyRope subclass.

In the case of adding a new key/value pair, when our #at:put: reaches the end of the list, the EmptyRope subclass can just implement it as
at: aKey put: anObject
self
setKey: aKey
value: anObject
next: EmptyRope basicNew.
self changeClassTo: Rope
And the only thing #removeKey: has to do in addition to the original #replace: trick is do a
   previousKnot changeClassToThatOf: self
to get the last link to be an EmptyRope.

Most of the rest of the code stays the same. We can reduce a couple of duplicates since the classes are now in a parent-child relationship, rather than a sibling one.

The at:put:/removeKey: test is the interesting one to rerun and see how things fair. First of all, we compare the new Rope approach to the original Chain one.


That shows some improvement across the board. The real test though, is how does it compare to the original IdentityDictionary?


The good news is, it's actually faster. Not by a huge amount. But it removes it from something you have to worry about.

I've replicated the Rope code up to the Open Repository (package TAG-Ropes). If you know you're using smallish dictionaries (size <= 15) and need to care about performance, then this might be the thing for you.

It would be interesting to see how such an approach fared in GNU Smalltalk or one of the other Smalltalks.

Wednesday, July 13, 2011

Syntax Highlighting your Blog with highlight.js

I've had some people ask me how I got syntax highlighting for Smalltalk code on my blogger blog. Here's what I did:

  1. Download highlight.js

    Go to Software Maniac's download page. Turn off the languages you're not interested in. Check the checkbox for Smalltalk. And hit the download button. You'll get a zip file that when unarchived has a whole directory structure of files, including the customized highlight.js file for your efforts. You need this customized version because it doesn't support Smalltalk in the stock version.

  2. Host it somewhere

    Find a net presence to host it at. Hopefully, you've either got your own or know someone who can host it for you. I was fortunate to fall into the second category.

  3. Update your blog template

    Edit your blog template (in Edit HTML link under the Design tab of your blog. You add the following lines near the end of your template

    <script src="http://yourhostedsite/highlight.pack.js" type="text/javascript">
    <script type="'text/javascript'">
    hljs.initHighlightingOnLoad();
    </script>

    You need to edit the yourhostedsite part appropriately.

  4. Add additional CSS for your blog

    You need to choose a css style you like from the downloaded package's style directory. Then use Design -> Template Designer -> Advanced -> Add CSS (at the bottom) and paste the contents of your preferred styles .css file into that.

  5. Enclose code blocks appropriately

    To have the highlighter apply to a chunk of code, you place your code in between pre and code tags. E.g.

    <pre><code>
    ...smalltalk code snippets...
    </code></pre>

At least... that's how I remember how I did it. It's been a month or so now. Hope that helps. And would love to hear of errors/corrections in the above.

Making Performance Comparison Charts

My last post was submitted just before embarking on a multi-week multi-state driving family vacation. While I was gone, I received a couple of requests regarding the code actually used to draw the performance comparison charts in that post.

I used CairoGraphics to do it. Those that know me are likely not surprised by this. The code is just a big workspace blob, and is somewhat procedural.

One of the challenges I wrestled with was how to turn numbers the numbers into percent increase or decreases. The easy method of just dividing one value by the other gives values that are not linear when drawn graphically. Or put another way, that is twice as fast draws differently than something that is twice as slow.

I used this fun snippet to convert from aTime and bTime values into such a comparison:


(((originalTime max: newTime) /
(originalTime min: newTime)) - 1.0) * (originalTime - newTime) sign


The large workspace blob that works with a variable called percents follows below, I put comment annotations to delineate the various sections. It may serve as a useful Cairo snippet.



| max min surface bandHeight xInc text extents y |

"compute some dimensions and create the surface"
max := (percents fold: [:a :b | a max: b]) ceiling max: 0.
min := (percents fold: [:a :b | a min: b]) floor min: 0.
surfaceHeight := (max - min) * 75 min: 300.
bandHeight := surfaceHeight / (max - min).
surface := ImageSurface format: CairoFormat argb32
extent: 600 @ surfaceHeight.
xInc := surface width / percents size.

surface newCairoContextWhile:
[:cr | | matrix |

"setup a default font"
cr
selectFontFace: 'Arial'
slant: FontSlant normal
weight: FontWeight bold.

"flip the coordinate system so we draw from the bottom up, rather than normal y is down style"
cr fontSize: 11.
cr translate: 0 @ surface height.
cr scale: 1 @ -1.
matrix := cr fontMatrix.
matrix scale: 1 @ -1.
cr fontMatrix: matrix.

"do rounded clip/border"
cr rectangle: (Point zero corner: surface extent) fillet: 10.
cr clipPreserve.
cr source: ColorValue black.
cr fill.

"shift up to base line"
cr translate: 0 @ min * bandHeight negated.

"draw columns"
percents keysAndValuesDo:
[:size :percent |
| rectangle baseHue x |
cr saveWhile:
[cr
rectangle: (Point zero
extent: surface width @ (percent positive ifTrue: [max] ifFalse: [min])
* bandHeight)
regular.
cr clip.
rectangle := Rectangle
left: (size - 1) * xInc + 3
right: size * xInc - 3
top: 5
bottom: -5.
percent > 0
ifTrue: [rectangle top: percent * bandHeight]
ifFalse: [rectangle bottom: percent * bandHeight].
cr rectangle: rectangle regular fillet: 5.
baseHue := (1 / 6 + (percent / 3) min: 1 / 3) max: 0.
cr source: (ColorValue hue: baseHue saturation: 1 brightness: 0.85).
cr fillPreserve.
cr source: (ColorValue hue: baseHue saturation: 0.85 brightness: 1).
cr strokeWidth: 4.
cr stroke].

"draw the column label"
text := size printString.
size = 1 ifTrue: [text := 'size=' , text].
extents := cr textExtents: text.
x := (size - 0.5) * xInc - extents width half.
y := percent positive ifTrue: [extents height negated - 2] ifFalse: [2].
y := y max: min * bandHeight + 2.
cr moveTo: x @ y.
cr source: ColorValue white.
cr showText: text].

"draw axis lines"
min to: max
by: 1 / 2
do:
[:n |
cr moveTo: 0 @ (n * bandHeight).
cr lineTo: surface width @ (n * bandHeight)].
cr source: ColorValue white.
cr strokeWidth: 0.25.
cr stroke.

"draw axis"
cr source: ColorValue white.
min to: max
do:
[:n |
n isZero
ifFalse:
[text := (n + n sign) printString , 'x'.
extents := cr textExtents: text.
y := ((n * bandHeight max: min * bandHeight + 4 + extents height half)
min: max * bandHeight - 4 - extents height half) - extents height half.
cr moveTo: 4 @ y.
cr showText: text.
cr moveTo: (surface width - 4 - extents width) @ y.
cr showText: text]].
cr moveTo: Point zero.
cr lineTo: surface width @ 0.
cr strokeWidth: 1.
cr stroke.

"draw label"
text := '#at:put: - Chain vs CompactDictionary'.
extents := cr textExtents: text.
cr moveTo: (surface width half - extents width half)
@ (max * bandHeight - 4 - extents height).
cr showText: text].

"write the file"
surface writeToPng: 'chart.png'