Oct 22 2012

Cleaning off my bookshelves

dastels @ 8:49 pm

And I have some books to get rid of. Yours for the cost of shipping:

Extreme Programming Applied : Playing to Win by Roy Miller and Ken Auer (2001, Paperback)

Extreme Programming Explored by William C. Wake (2001, Paperback)

Integrated Digital Electronics by Walter A. Triebel (1979, Hardcover)

Extreme Programming Installed by Chet Hendrickson, Ann Anderson and Ronald E. Jeffries (2000, Paperback)

Extreme Programming Adventures in C# by Ronald E. Jeffries (2004, Paperback, Revised)

Operating System Concepts by James L. Peterson and Abraham Silberschatz (1988, Hardcover)

Languages and Machines : An Introduction to the Theory of Computer Science by Thomas A. Sudkamp (1988, Hardcover)

An Introduction to Numerical Computations by Sidney J. Yakowitz and Ferenc Szidarovszky (1997, Paperback)

Extreme Programming Explained : Embrace Change by Kent Beck (1999, Paperback)

Planning Extreme Programming by Martin Fowler and Kent Beck (2000, Paperback)

Rails Recipes by Chad Fowler (2006, Paperback)

Java Design : Building Better Apps and Applets by Mark Mayfield, Jon Kern and Pete Coad (1998, CD-ROM /
Paperback):

Effective Java Programming Language by Joshua Bloch (2001, Paperback)

Cocoa in a Nutshell : A Desktop Quick Reference by Michael Beam and James Duncan Davidson (2003, Paperback)

Calculus with Analytic Geometry by Sherman K. Stein (1987, Hardcover)

Discrete Mathematics and Its Applications by H. Rosen (1988, Hardcover)

Advanced Rails Recipes : 84 New Ways to Build Stunning Rails Apps by Rails Community and Mike Clark (2008, Paperback)

Agile Software Development by Alistair Cockburn (2001, Paperback)

Database Management : Concepts, Design and Practice by Esen A. Ozkarahan (1990, Hardcover)

Microprocessors and Microcomputers : Hardware and Software by Ronald J. Tocci and Lester P. Laskowski (1982, Paperback)

The Wiki Way : Collaboration and Sharing on the Internet by Ward Cunningham and Bo Leuf (2001, Paperback)

Pro Drupal Development by Matt Westgate and John K. VanDyk (2007, Paperback, New Edition)

Extreme Programming Examined by Giancarlo Succi and Michele Marchesi (2001, Paperback)

Extreme Programming in Practice by Robert Martin and James Newkirk (2001, Paperback)

Extreme Programming Explored by William C. Wake (2001, Paperback)

Practices of an Agile Developer : Working in the Real World by Venkat Subramaniam and Andy Hunt (2006, Paperback)

Data Crunching : Solve Everyday Problems Using Java, Python, and More by Greg Wilson (2005, Paperback)

Building Online Communities with Drupal, phpBB, and WordPress by Jared W. Smith, Mike Little and Robert T. Douglass (2005, Paperback, New Edition)


Apr 26 2012

Environment headers

dastels @ 12:42 pm

I’m working on a web app in Clojure and finding or (re)inventing various useful bits of code, which I’ll be posting here.

To start, here a few bits of code to create a banner identifying the environment you’re looking at.

First, read in the environment from a property file and create ways to query it.

(def env-props (read-properties "env.properties"))
(def environment (.getProperty env-props "env"))
(def development? (= "development" environment))
(def staging?     (= "staging" environment))
(def production?  (= "production" environment))

I decided to put the banner up when logged in as admin, so that I’m keenly aware of where I am when I have that level of control. I also wanted the banner displayed everywhere other than production regardless of the user. (The grid-12, alpha, and omega classes are part of the 960-system css framework I’m using.)

(unless (and (not (any-role-granted? :admin))
             production?)
  [:div#environment-banner.grid-12
    [:div {:class (str "grid-12 alpha omega " environment)}
      (str "============>>> " environment " <<<============")]])

Finally, some css to make it standout. I went with a traffic light color scheme.

div#environment-banner div {
    text-align: center;
    text-transform: uppercase;
    font-weight: bolder;
    padding-top: 10px;
    padding-bottom: 10px;
}

div#environment-banner div.development {
    background: green;
    color: white;
}

div#environment-banner div.staging {
    background: orange;
    color: black;
}

div#environment-banner div.production {
    background: red;
    color: white;
}


Nov 14 2010

Embeddable Lisp for iOS apps

dastels @ 6:49 pm

Overview

This is a preliminary description of this language/system. The doc.html file in the repo will always be the most up to date.

You can get the code to play with at github.com/dastels/DSL.

DSL is a simple, interpreted, scheme inspired lisp inplementation
in ObjectiveC for embedded scripting in iOS apps, although it is
equally applicable in OSX apps. There is no reliance on Cocoa or
CocoaTouch, just foundation.

The primary motivation was the need to implement rules in a card
game at a significantly high level of abstraction in a way that
wouldn’t require recompiling (i.e. could be stored in the
database).

Syntax

The syntax is a simple version of scheme with lexical scoping and a
minimal set of builtin special forms. One notable omission is macro
support. Macros may be supported at some later date, if and when
they’re required.

Data Types

DSL has support for a basic set of data types.

Integer

DSL supports unsigned integers, in decimal notation. Size
refelects the underlying ObjC int type.

String

Strings in DSL use double quotes, and consist of a sequence of
characters other than double quotes.

Boolean

While boolean values are used regularly, literals are less common.
The most common use is probably in the default clause in
a cond form. When used, #t indicates true
and #f indicates false.

Cons Cell

As in most, if not all Lisps, the core data type is the cons cell.
It is simply a pair of values, traditionally known
as car and cdr, or more recently head and
tail. DSL uses the car and cdr
notation.

A literal cons cell is made using two values in parentheses,
separated by a period. Otherwise known as a dotted pair:

  (1 . 2)

The constant nil represents an empty, aka NULL cons
cell. nil is always equal to nil as well
as empty lists. The head and tail of nil are
also nil.

List

A list is sequence of cons cells, linked through tails, with the
final tail being nil. The standard notation is used, a sequence of
values separated by whitespace and enclosed in parentheses:

(1  2 3)

Internally, the list (1 2 3) is equivalent to:

(1 . (2 . (3 . nil)))

Symbol

Symbol is another main building block, the other being list. A
symbol is essentially an interned string. Notationally, they are a
series of characters, without enveloping double quotes. Allowable
characters are letters, digits, :, -, ?, and !. By convention, colon
is used in ObjC selectors, dash is
used as a word seperator to enhance readability, a trailing
questionmark indicats a predicate, while a trailing exclaimation
mark indicates womething with a side effect. Symbols must start with
a letter, and are case sensitive.

Association List

DSL supports association lists, which are much like dictionaries. They are implimented as a list of dotted pairs, e.g.

((name . "Dave") (language . "Lisp"))

Use of association lists are covered later in the section on builtins.

Runtime

Your interface to DSL is though strings containing source
code. These string get parsed into sexpressions which are then evaluated.

Symbols are stored in a symbol table. Symbol tables are managed in
a stack. Local scopes are created by pushing a new stmbol table
onto the stack. New bindings are placed in the top table in this
stack. When the value of a symbol is looked up, the search starts
with the top table and if it is not found there, the stack is
traversed back to the global symbol table. If no value is found by
then, the symbol is unbound. A new local scope is created upon the
entry to functions and lets, and destroyed on the respective exit.

Unlike some modern lisps, in DSL symbols have a single binding (as
opposed to separate value, function, etc. bindings).

Integration

ObjectiveC objects can be wrapped and their properties can be read
and writted (depending on the property definition). The functions
for doing that are described below.

In ObjC, you wrap an object simply using the DslObject class:

+ (DslObject*) withObject:(id)anObject;

For example:

[DslObject withObject:user]

You can then use the functions described later to use this object
from your lisp code.

Extension

You can add your own function to DSL in order to integrate DSL into
your app.

To add your own builtin functions/forms you instantiate a
DslBuiltinFunction to point to your object/method. You then must
bind it to a name. For example:

[DSL bindName:@"car"
     toTarget:self 
     andSelector:@selector(car:)]];

When a builtin is evaluated, it’s arguments are not automatically
evaluated first. If they should be, the builtin has to do it itself:

- (DslBoolean*) logicalNot:(DslCons*)args
{
  return [DslBoolean booleanWith:![[args.head eval] booleanValue]];
}

-initialization hooks … still need to figure this out

The class Dsl provides many functions that you will
need when writing your own builtins.

- (DslExpression*) parse:(NSString*)codeString

This takes the lisp code in codeString, which is assumed
to contain a single sexpression, and parses it. The resulting
sexpression is returned.

DslExpression *sexpr = [DSL parse:code];
if (sexpr) {
  [DSL eval:sexpr];
}

- (DslExpression*) eval:(DslExpression*)sexp

This function evaluates the provided sexpression and returns the result. You can see an
example in the previous section.

- (DslSymbol*) internalIntern:(NSString *)name

This is how you can convert an NSString* into a symbol.
The main use for this is to set up bindings using the next function.

- (DslExpression*) bind:(DslSymbol*)symbol to:(DslExpression*)value

Bind a value to a symbol in the most local scope. You can use this
(in conjunction with internalIntern
and DslObject) to make objects from your system available
in lisp.

- (DslExpression*) valueOf:(DslSymbol*)symbol

Look up the value of a symbol, starting in the most local scope and
proceeding back to the global scope until a binding for the symbol
is found, or the options are exhausted.

You can use this and the previous functions in a fashion similar to:

DslSymbol *gameName = [[DSL internalIntern:@"game"] retain];
DslObject *gameObject = [[DslObject withObject:game] retain];
if ([DSL valueOf:gameName] == nil) {
  [DSL bind:gameName to:gameObject];
}

- (DslCons*) makeList:(DslExpression*)firstObject, ...

Creates a list containing the arguments. Note that the arguments
need to be terminated by nil.

Consider this snippet from the definition
of reduce:

while ([data notNil]) {
  result = [self apply:function to:[self makeList:result, data.head, nil]];
  data = (DslCons*)data.tail;
}

- (DslExpression*) apply:(DslFunction*)func to:(DslCons*)args

Very simply, invoke the given function, passing it the provided
list as it’s arguments. This can be seen in the example for makelist:.

- (DslExpression*) getNth:(int)n from:(DslCons*)list

Retrieve the nth item in a list.

- (int) internalLength:(DslCons*)list

Get the length if a list, returned as an int rather
than as a lisp integer object.

- (DslExpression*) loadFile:(NSString*)filebasename

Load a file of lisp code. This is an ObjectiveC access point to
the load function described below.

testing

The test framework reads files containing comments, expressions and the
expect result of evaling them, separated by blank lines. Each such
triplet is separated by a line containing 4 hyphens. For example:

Applying Single Argument Lambda

(apply (lambda (x) (+ x 2)) 40)

42
----
Applying Multiple Argument Lambda

(apply (lambda (x y) (+ x y)) 40 2)

42
----
Simple Defun

(do 
  (defun foo ()
         42)
  (foo))

42
----
Complex defun

(do
  (defun plusone (x)
         (+ x 1))
  (list (plusone 0) (plusone 1) (plusone 2)))

'(1 2 3)
----
More Complex defun

(do
  (defun fib (x)
         (cond ((= x 0) 1)
               ((= x 1) 1)
               (#t (+ (fib (- x 1)) (fib (- x 2))))))
  (list (fib 0) (fib 1) (fib 2) (fib 3) (fib 4) (fib 5) (fib 6)))

'(1 1 2 3 5 8 13)

As tests run, the name of each file as well as the status of each
indivisual test is logged to the console, like so:

2010-10-31 16:39:05.632 Repl[79801:207] Running: logic-functions
2010-10-31 16:39:05.634 Repl[79801:207] FAIL: Or with no args
2010-10-31 16:39:05.634 Repl[79801:207] PASS: Or with one args

For failures, more detail is logged at the end of the test run:

2010-10-31 16:39:05.658 Repl[79801:207] Failures
2010-10-31 16:39:05.658 Repl[79801:207] ----
2010-10-31 16:39:05.658 Repl[79801:207] FAIL: Or with no args
2010-10-31 16:39:05.659 Repl[79801:207] (or)
2010-10-31 16:39:05.659 Repl[79801:207] Expected true but got false

Finally, a summary is logged:

2010-10-31 16:39:05.660 Repl[79801:207] Time: 0.093374 sec, 187 Tests, 186 Passes, 1 Failures

Builtins

intern

(intern STRING) => SYMBOL

This makes a symbol from a string in the most local symbol table.

quote

Avoid evaluating the argument.

(quote SEXPR) or as a shorthand 'SEXPR

For example, (+ 1 2) => 3, but
'(+ 1 2) => (+ 1 2).

lambda

(lambda (PARAMS) BODY) => FUNCTION

(lambda (x) (+ 1 x))

Create an anonymous function. This is specifically useful for
providing functions to iterator or application functions. Mostly
useful for short functions.

(map (lambda (x) (+ x x)) '(1 2 3)) => (2 4 6)

defun

(defun SYMBOL (PARAMS) BODY)

(defun double (x) 
       (+ x x))

(map double '(1 2 3)) => (2 4 6)

Create a named function.

apply

(apply FUNCTION ARGUMENTS)

(apply (lambda (x) (+ 1 x)) 2) => 3

(defun add1 (x) (+ 1 x))

(apply add1 2) => 3

This applies a function to set of arguments. This functionallity is
core to the system, and is used internally a lot. None the less, it
is sometimes useful explicitly.

do

(do BODY)

(do (+ 1 1) (* 1 1)) => 1

Evaluate a sequence of expressions, in order, returning the result of the final one.

This is used implicitly in several places: function bodies, let
bodies, and cond clause bodies. It’s also sometimes useful to use
it explicitly.

let

(let ((NAME VALUE)...) BODY)

(let ((x 5)
      (y 2)) 
     (+ x y))

=> 7

let creates a local scope in which to place the
bindings and evaluate BODY (which is an
implicit do)

Each VALUE is evaluated and the result bound to the
corresponding NAME in sequence (not in parallel as in some
dialects). This means you can have things like:

(let ((a 2)
      (b (+ a 1))
     b)

=> 3

cons

(cons 'a 'b) => (a.b)

(cons 'a '(a b)) => (a b c)

This creates a cons cell with the arguments as it’s car and cdr.

list

(list ITEMS) => (ITEMS)

(list 1 2 3) => (1 2 3)

Create a list from the arguments.

car/cdr

caar/cadr/cdar/cddr

caaar/caadr/cadar/caddr/cdaar/cdadr/cddar/cddr

(car '(a b c)) => a

(cdr '(a b c)) => (b c)

This is the tradion family of list access functions.
and are the core functions:

  • car returns the head of the cons cell argument
  • cdr returns it’s tail

The longer forms combine car and cdr, for
example: (caddr a) is the same as (car (cdr (cdr
a)))
and so on.

length

(length LIST) => INTEGER

(length '(a b c d)) => 4

Returns the length of LIST. This is the linear sequence
of cons cells through the tail of each.

map aka collect

(map FUNCTION LIST) => LIST

(map (lambda (* 2 x)) 
     '(1 2 3))

=> (2 4 6)

Applies FUNCTION to each element of the list, in order,
returning in a list of the results of each
application. The FUNCTION can be the name of a
defined/builtin function of a lambda.

filter aka select

(filter PREDICATE LIST) => LIST

(filter odd? '(1 2 3)) => (1 3)

Select all items from LIST that satisfy the predicate
function (returns a boolean). PREDICATE is applied, to each
element of the list, in order, resulting in a list of the original
items for which the function returns true.

reduce aka inject

(reduce FUNCTION SEED LIST) => VALUE

(reduce FUNCTION LIST) => VALUE

(reduce + 0 '(1 2 3 4)) => 10

(reduce + '(1 2 3 4)) => 10

(reduce (lambda (a b) (if (< a b) a b)) '(7 2 8 4 2 9)) => 2

The second form uses the first element of the list as it's seed value. I.e. it is equivalent to:

(reduce FUNCTION (car LIST) (cdr LIST)) => VALUE

any? aka detect

(any? PREDICATE LIST) => BOOLEAN

(any? odd? '(1 2 3)) => #t

(any? odd? '(0 2 4)) => #f

Check if any items in the list satisfy the predicate function, by
appling the function to each element of the list, in order, until
one returns true or all items have been
considered. If PREDICATE returns #t for an
item then any? returns #t, otherwise it returns
#f.

all?

(all? PREDICATE LIST) => BOOLEAN

(all? odd? '(1 3 5)) => #t

(all? odd? '(1 2 3)) => #f

Check if all items in the list satisfy the predicate function, by
appling the function to each element of the list, in order, until
one returns false or all items have been
considered. If PREDICATE returns #f for an
item then all? returns #f, otherwise it returns
#t.

if

(if BOOLEAN TRUE-SEXPR)

(if BOOLEAN TRUE-SEXPR FALSE-SEXPR)

(if (= x 5) (do-something))

(if (= x 5) 'a 'b)

If BOOLEAN evaluates to true, the TRUE-SEXPR is
evaluated. If BOOLEAN evaluates to FALSE,
the ELSE-SEXPR is, if one is supplied (if there isn't one,
then nothign is done).

cond

(cond (BOOLEAN BODY)...)

(cond ((< x 3) 'small)
      ((< x 7) 'medium)
      ((< x 10) 'large)
      (#t 'unknown))

This is the traditional multi branch lisp conditional. The
arguments to cond are two element lists consisting of
something that evaluates to a boolean and an arbitrary
sequence of sexpresions (the BODY. The boolean expression of each pair, in order, is
evaluated until one results in true or all have been
evaluated. If one evaluates to true, the corresponding
sexpression is evaluated in an implicit do and the
result becomes the result of the cond expression.

or, and, not

logical functions

(and x y ...)

(or x y ...)

(not x)

not takes a single
argument and results in it's logical negation.

or/and take any number of args.
evaluates to true only if all arguments evaluate
to true. With no arguments it results
to true. or results in true
if any of it's arguments evaluate to true. With no
arguments it results in false.

Both and and or evaluate their arguments
only until the result is known. I.e. and will stop
evaluating it's arguments as soon as one evaluates
to false, and or will stop as soon as an
argument evaluates to true.

+, -, *, /, %

These are the standard arithmetic operators (% is modulus)

Other than %, these take any number of arguments. For
example, (- 10 3 2) is the same as (- (- 10 3)
2)
, or infix: (10 - 3) - 2.

<, =, >

The standard relational operators, e.g. (< a 5). They
all evaluate to boolean value.

get-string, get-integer, get-boolean

(get-TYPE OBJECT PROPERTY)

(get-string user 'name)

Access properties from wrapped ObjectC objects.

set-string, set-integer, set-boolean

(set-TYPE OBJECT PROPERTY VALUE)

(set-string user 'name "Dave")

Set properties in wrapped ObjectC objects.

Note that none of the functions that create association lists
gaurentee any order on the dotted pairs in the result.

acons

(acons KEY VALUE A-LIST)

(acons x y '((a . 1) (b . 2)))
=> ((x . y) (a . 1) (b . 2))

Prepend a dotted pair (KEY . DATA) to A-LIST.

pairlis aka zip

(pairlis KEYS DATA)

(pairlis KEYS DATA A-LIST)

(pairlis '(one two) '(1 2)) => ((one . 1) (two . 2))

(pairlis '(one two) '(1 2) '((three . 3) (four . 4))) => ((one . 1)
(two . 2) (three . 3) (four . 4)))

Make dotted pairs from items in the two argument lists, matched
pairwise.

assoc

(assoc KEY A-LIST)
=> (KEY . DATA)

(assoc 'r '((a . b) (c . d) (r . x) (s . y) (r . z)))
=> (r . x)

Search A-LIST for the first pair whose car
is KEY.

rassoc

(rassoc DATA A-LIST)
=> (KEY . DATA)

(rassoc 'a '((a . b) (b . c) (c . a) (z . a))) => (c . a)

Search A-LIST for the first pair whose cdr
is DATA.

load

(load FILE-BASENAME)

(load "game-ai")

(load 'game-ai)

Loads a file of sexpressions into memory, then evaluates then in
the binding context of where load was called. Returns
a list of the results of this evaluation. The file will generally
contain defintions and the evaluation will load them into the symbol
table. Occasionally you may have use for the evaluated results. The
symbol table in which any binding occurs is the symbol table that
was local when load was called.

The file must have the extension of lsp and be a
project resource. It is located using:

[[NSBundle mainBundle] 
    pathForResource:filebasename 
    ofType:@"lsp" 
    inDirectory:nil]

Oct 25 2010

iPhone version of Rules app

dastels @ 10:29 pm

I have an iPhone Comprehensive Magic: the Gathering rules app ready for testing. See a video clip of it by clicking the image below. If you want to help beta test it, contact me.




Oct 15 2010

Latest project: MTG rules iPad app

dastels @ 11:41 pm

Here’s a peek at an app I’m currently working on. The comprehensive rules for Magic: the Gathering. I think all that’s left is search, icon, and splash screen. Will Wizards of the Coast let me put it on the App Store? Who knows.




Click the image above to play the video


Aug 10 2010

Initial comparison of iPhone 3GS & 4 cameras

dastels @ 11:15 pm

As some of you know, I maintain a food blog in which I try to have a lot of food porn … um … photos. Mix that with restaurant reviews/reports. The upshot is that I take the pics at restaurants with my iPhone. My iPhone 3GS. You know … the one with the (now) sucky camera. It sucks in low light conditions, producing grainy pics. It doesn’t have a flash. So dinners tend not to be too photogenic.

Enter the iPhone 4 with it’s low light sensor and flash (not to mention a 5MP sensor compared to the 3GS’ 3MP). I put off buying an iPhone4 for a long time (in Apple-years). I finally decided to go for it, in the name of journalistic integrity. And a desire to simply take more pictures. Sure, I have a better camera (5MB, glass optics, optical zoom, flash, etc) but it doesn’t fit in my pocket, not to mention it being unable to play Plants vs. Zombies.

So tonight before dinner, I stopped at the nearby (oh so deviously nearby) Apple Store and (with impressive efficiency from the staff) got an iPhone4. Since I a) wanted it for taking pictures, specifically in low lighting restaurants, and b) I had my iPhone 3GS in my pocket … I did a small exercise. At dinner (Cumin in Wicker Park … to be appear on CafeSnobisme) I took pictures of our dishes as I am wont to, with each phone. Here, side by side are the results. These are raw from the phones, uncropped and untweaked in anyway other than sizing.

3gs appitizer4 appitizer

3gs sambar4 sambar

3gs bread4 bread

3gs rice4 rice

3gs malai kofta4 malai kofta

3gs fish vindaloo4 vindaloo

All I can say is “Holy shit … that’s pretty good!”


Aug 10 2010

Time flies when your life is being reinvented

dastels @ 10:38 pm

Last post was Jan 18! I’m ashamed of myself. Then again my life totally changed (for the better) on Jan 19 and I’ve been busy catching up. I might write about that sometime .. but not now, and not here.

Update. I’m no longer with Engine Yard. I’m in the process of moving to Chicago and pursuing several opportunities there, and consulting at Groupon (with my good friends at Obtiva) in the interim.

And hopefully, I’ll be writing more here again. I have lots of ideas in the queue.


Jan 18 2010

Go Behave!

dastels @ 3:30 pm

For my Google peeps. My buddy @stesla is giving a Google tech talk tomorrow at 11:30 in Paramiribo (techtalk 42) on BDD in Go including an introduction to the BDD framework her wrote: gospecify . We’ll be there for lunch afterward as well.


Aug 22 2009

Rumble off to a rocky start

dastels @ 7:56 pm

Nancy hit the ground running, whereas I ground to a halt. Ruby worked fine. Rails worked fine. Mysql worked fine. Rails wouldn’t talk to Mysql, though. After much head scratching, cursing, and frantic searching for liquor… I hit upon the crux of the problem. While I was installing the mysql gem… it wasn’t getting used. /usr/local/lib/ruby/1.8/mysql.rb was! To quote @thebloggess “The Fuck, Victor?!”

So a quick

sudo mv /usr/local/lib/ruby/1.8/mysql.rb /usr/local/lib/ruby/1.8/fucking-bogus-mysql.rb

later (with the mysql gem installed) I was up & running. A bunch of Ruby, Erb, CSS, and Javascript later and it’s looking pretty good.


Aug 22 2009

Rumblin’

dastels @ 7:44 pm

All my blogging energies have gone into the EngineYard blog for the last while.

This is a just a note to say that Nancy & I are once again taking part in the rails rumble. This time focussing on getting shit done. Pure & simple. No detailed design or up front thinking.. just a general idea. No testing, think it up & code it up. And now we’re past the half way point and things are taking shape nicely.

We’re having fun and it’s something that might be fun to hack at afterward.


Next Page »


Steve Jobs Memorial

Stay Hungry, Stay Foolish!
Steve Jobs

In memory of Steve Jobs