(defun SkuSolve (partial_solution) (let ((sudoku (SkuInitialize (SkuNew) partial_solution))) (printf "starting with: \n%s\n" (SkuPrint sudoku)) (printf "\nfound solution:\n%s\n" (SkuPrint (SkuFindSolution sudoku)))))
In this posting, you'll see some examples of non-trivial class and
instance manipulation which completely avoid the topic of methods.
Although the SKILL++ object system provides a powerful method
manipulation capability, you are not required to understand anything
about methods to implement applications that operate on classes.
Non-trivial slot initialization
In the previous posting we saw how to use @initform to
initialize instance slots to constant/default values. But
the @initform can do more than provide constant defaults.
The expression provided by @initform is actually SKILL++
code which runs every time an instance is created
with makeInstance. The code in this SKILL++ expression
is allowed to reference any global or local function or variable, such
as functions defined within an labels as shown in the
The SkuSudoku class defined below represents the sudoku
board itself. It has a list of 81 cells, a list of 9 columns, a list
of 9 rows, and a list of 9 3x3 blocks. Notice that the
class SkuSudoku is defined inside a (labels
...) which defines a local function named repeat,
and that local function is referenced inside
the @initform expression.
In particular, in the class definition, the three slots
(rows, columns, and b3x3s) each
have a different @initform expression which each
reference the local function repeat.
(labels ((repeat (n unary "xU") ;; call the given function N number of times, ;; collecting the return values. (when (plusp n) (cons (unary (sub1 n)) (repeat (sub1 n) unary))))) (defclass SkuSudoku () ((cells @initform nil) (rows @initform (repeat 9 (lambda (n) (makeInstance 'SkuRow ?index n)))) (columns @initform (repeat 9 (lambda (n) (makeInstance 'SkuColumn ?index n)))) (b3x3s @initform (repeat 9 (lambda (n) (makeInstance 'SkuB3x3 ?index n)))))))
A call to the SKILL primitive makeInstance such as an
evaluation of the expression (makeInstance 'SkuSudoku)
will create an instance of the class and will initialize the 4 slots
by evaluating the 4 respective @initform expressions.
Furthermore, the expressions will be evaluated such that the local
function repeat is defined.
Encapsulation through lexical scoping
Note that the feature of encapsulation (the ability to limit the
visibility of functions like repeat) is provided by
SKILL++ lexical scoping. This is different from the C++/Java paradigm
which forces you to use the object system to implement encapsulation.
In SKILL++, encapsulation works with or without the object system, so
all SKILL++ programs can take advantage of it, not just
Initialization using a factory function
There is a limit to how much initialization is possible from
the @initform expressions. In particular
the @initform expressions are not allowed to reference
each other. Furthermore, you have no guarantee in which order the
initialization expressions will evaluate. This lack of guarantee is
especially important when define classes using single inheritance, and
even more important in the case of multiple inheritance.
All @initform expressions need to be mutually independent
expressions which only depend on the environment of
the defclass and not on each other.
Skill++ programs typically create instances of classes in one of two
ways: either by a direct call to makeInstance or by a
call to an intermediate function which
calls makeInstance. Such an intermediate function is
called a factory function. A benefit of a factory function is
that the function may also preform any additional initialization as
necessary for the correct behavior of the program.
There is still another advantage to using a factory function rather
than a direct call to makeInstance. The function can
initialize slots so that their initial values depend on each other.
In the case of a properly initialized sudoku board, the cells, rows,
columns, and 3x3 blocks all depend on each other in a particular way.
There is a list of 81 cells cells slot, and each of these
cell objects is in the correct row, column, and 3x3 block. The sudoku
board cannot be initialized simply by the @initform
expressions. The job of SkuNew is to assure that the
cells, rows, columns, and 3x3 blocks reference each other properly.
A factory function typically does the following:
Creating a structurally correct sudoku board
We want to define such a factory function
named SkuNew which allocates, initializes, and
returns an instance of the SkuSudoku class.
The function, SkuNew, does the actual allocation via a
call to makeInstance as well as setting up the structure
of the board independent of the actual content of the particular
sudoku solution. In particular SkuNew assures
that each cell can easily access its row, column, and 3x3 block and
that each row, column, and 3x3 block can easily access its cells.
(defun SkuNew () ;; allocate a blank-slate instance of SkuSudoku representing an empty ;; sudoku board (let ((sudoku (makeInstance 'SkuSudoku))) (let ((index 0)) (foreach row sudoku->rows (foreach col sudoku->columns (let ((cell (makeInstance 'SkuCell ?index index++)) (b3x3 (nth (xplus (xtimes 3 (xquotient row->index 3)) (xquotient col->index 3)) sudoku->b3x3s))) ;; add the cell to the list of sudoku cells. sudoku->cells = (cons cell sudoku->cells) ;; tell the cell which row, column ;; and 3x3 block it belongs to. cell->row = row cell->column = col cell->b3x3 = b3x3 ;; tell the row, col, and 3x3 block ;; that this cell belongs to it. row->cells = (cons cell row->cells) col->cells = (cons cell col->cells) b3x3->cells = (cons cell b3x3->cells))) ;; return the new instance. sudoku))
Feeding in the partial solution
Finally, given an initialized and consistent object which represents
the sudoku board, it is necessary to feed in a given partial
solution in preparation for running the solution
algorithm, SkuFindSolution. The
function SkuInitialize iterates through the rows and
columns of a given SkuSudoku instance, filling some of
the cells with a number from 0 to 9 as per the given partial solution.
(defun SkuInitialize (sudoku partial_solution) ;; feed in the partial solution (foreach (sudoku_row solution_row) sudoku->rows partial_solution (foreach (cell solution) sudoku_row->cells solution_row (when (numberp solution) cell->value = solution))) sudoku)
The sudoku instance initialization protocol
The two functions together form the sudoku initialization protocol:
(SkuInitialize (SkuNew) '((5 3 ? ? 7 ? ? ? ?) (6 ? ? 1 9 5 ? ? ?) (? 9 8 ? ? ? ? 6 ?) (8 ? ? ? 6 ? ? ? 3) (4 ? ? 8 ? 3 ? ? 1) (7 ? ? ? 2 ? ? ? 6) (? 6 ? ? ? ? 2 8 ?) (? ? ? 4 1 9 ? ? 5) (? ? ? ? 8 ? ? 7 9)))==>stdobj@0x1d454234
The printed object such as stdobj@0x1d454234 is how an
instance of a SKILL++ class is printed.
Displaying an instance
The default way an instance of a SKILL++ class prints is not very
informative. We need to write a special purpose
function, SkuPrint to display the partial (or full) state
of the sudoku board.
The following function SkuPrint generates a string
representing the ASCII representation of the sudoku board. The string
contains \n characters so you'll have to use (printf "%s"
...) or a similar function to print it so it is human readable.
(defun SkuPrint (sudoku) (let ((divider "+-----------------+\n")) (strcat divider (buildString (foreach mapcar row sudoku->rows (strcat "|" (buildString (foreach mapcar cell row->cells (if cell->value (sprintf nil "%d" cell->value) " ")) ;; separate the cells with | "|") "|")) ;; separate the lines with \n "\n") "\n" divider)))
(printf "%s\n" (SkuPrint (SkuInitialize (SkuNew) '((5 3 ? ? 7 ? ? ? ?) (6 ? ? 1 9 5 ? ? ?) (? 9 8 ? ? ? ? 6 ?) (8 ? ? ? 6 ? ? ? 3) (4 ? ? 8 ? 3 ? ? 1) (7 ? ? ? 2 ? ? ? 6) (? 6 ? ? ? ? 2 8 ?) (? ? ? 4 1 9 ? ? 5) (? ? ? ? 8 ? ? 7 9)))))==>+-----+-----+-----+|5|3| | |7| | | | ||6| | |1|9|5| | | || |9|8| | | | |6| ||8| | | |6| | | |3||4| | |8| |3| | |1||7| | | |2| | | |6|| |6| | | | |2|8| || | | |4|1|9| | |5|| | | | |8| | |7|9|+-----+-----+-----+
Enhancements in IC615
The approach and the actual code shown
here works in versions of SKILL++ using IC5033, IC5141, and IC61 up to
and including IC615. However, there are some new features in IC615
which make initialization and presentation of SKILL++ objects easier
and more flexible. To completely understand these and other new
features, you'll need to understand something about generic functions
and methods, which SKILL for the Skilled has not addressed yet,
but here is a little taste.
(printf ... "%L")
In this posting you have seen some examples of non-trivial SKILL++
class allocation and initialization using both @initform
and by a factory function. You've also seen a way to present SKILL++
objects in human readable form depending on the semantics of your
In the next posting of SKILL for the Skilled
we'll finally look at how to implement the
function SkuFindSolution which is the last function
needed to complete the implementation of SkuSolve