1

So I have a class to create in smalltalk called LazyMatrix. The class only has 1 instance variable and cannot be a subclass of anything but Object. The instance variable of LazyMatrix is called block and must be a back. I initialize LazyMatrix like this:

initialize
block:=[nil]

There will be a method for setting values

setRow:column:value:

This method will redefine the block by setting the new block as [#(i j value).[nil]]. Each subsequent call adds an array of 3 to the block, so it expands like [#(i j value).[#(i j value).[nil]]] much like an s-expression or "lazy list".

So I need to access the head of this block (i.e. [#(i j value) ) as well as the tail of this bock (i.e. [#(i j value).[nil]] ). How do I do this in smalltalk? I know calling value on the block will return the tail... now I need to return the head.

Wes Field
  • 3,291
  • 6
  • 23
  • 26

2 Answers2

4

Adding an array to your block doesn't get you anywhere. Think in terms of behavior, not about data structures.

I guess your teacher did provide you with the overall idea: given a row and column, answer the associated value; otherwise ask the "tail" for the answer.

That is literally how you would implement it in Smalltalk using a block closure (where the "tail" is the previously set block). Inside the block, put code that does the testing and answering and tail recursion. Do not make a data structure, tail and head are just metaphors to reason about this style of coding.

I just implemented your LazyMatrix in Squeak, it is just a few lines of code. Cute example, indeed. And no arrays or collections involved, at all.

Hint: The key to this puzzle is realizing that every call to setRow:column:value: can create a new block, which is independent of all the previously created blocks.

codefrau
  • 4,583
  • 17
  • 17
  • So I am initializing it like 'block:= [ :i :j| 0 ]'. Then 'setRow:column:value: | arr tail head | arr:= Array new:3. arr at:1 put:i. arr at:2 put:j. arr at:3 put:val . tail:=block. head:= [ :r :c | arr. ( (r=(arr at:1)) and: (c=(arr at:2)) ) ifTrue: [ (arr at:3) ] ifFalse: [ tail ] ]. block:=head.' Is this a good way to do it? You mentioned not using arrays at all... How would I do something like that? Thank you for the help. – Wes Field Apr 10 '13 at 22:04
  • 1
    The initialization is correct. If you would ask that first block for any row or column it will answer zero. Now, imagine you had just a single other block, that would answer e.g. 42 if the row was 3 and the column 5. For any other row or column it would evaluate the first block instead. That is very easy to write down, and will emulate a matrix of all zeros and a single 42 in one place. Now if you make a third block that answers e.g. 69 if the row is 5 and the column is 3, otherwise it evaluates the second block, you will have a matrix with 2 non-zero values. See? No arrays involved. – codefrau Apr 11 '13 at 07:17
  • Ok, I wrote it up like that and everything seems to work. Just one more question... If I wanted to write methods that would add/multiply all the values stored in the matrix by some scalar s, what would be the best way to do this? Should I use some form of inject: ? Thanks. – Wes Field Apr 11 '13 at 11:21
  • There is no list, so inject could not possibly work. Again, this assignment (as I understand it) is purely about behavior, not data structures. So ask yourself, what is the desired behavior? That is, if you think of the object as a black box, what would it answer after a series of inputs, not worrying about how it is all stored? (Hint: read up on functional programming, which is what we're doing here, just using an object-oriented language. This is not how you would normally implement a lazy matrix in Smalltalk, but it is a good learning exercise.) – codefrau Apr 11 '13 at 15:57
  • One more hint: if you set a row/column to a certain value, and then to a different value, it works fine. The value set last will be answered. Now think about the "stored value". How was it changed? – codefrau Apr 11 '13 at 16:09
  • A correction to the comment about using some form of inject. – Jim Sawyer Jan 18 '23 at 21:56
  • The reason not to use some form of inject is that inject is a reducer, whereas to add or multiply by a scalar you would need a transformer--i.e. some form of #collect. The difficulty in doing this is that there appears to be no list--just a device that can answer the values most recently stored at specific locations, but nothing else--e.g. it hasn't the properties of a rectangular coordinate system. This is an illusion. There actually is a list, it just cannot yet be traversed for general purposes. Needs a way to #do: something. – Jim Sawyer Jan 18 '23 at 23:28
0

I think you can go in two ways here. The first one (which I wouldn't recommend) is to model your s-expressions as lists in lambda calculus. You basically use blocks as functions and you are done. Here you can fond an explanation of writing lists using lambda calculus. As an example, in Smalltalk you would write

empty = λfx.x

as

empty := [:f :x | x].

Now, going that road would be basically writing a functional program in an OO language, which I wouldn't do. If you want to use the symbolic approach for lists then you should model it with objects. Have a EmptyList class and a Cons class (Cons would have an element and a list inst var), so that you create a list by doing:

listWithOne := Cons element: 1 list: EmptyList new.

The head and tail methods can be trivially written in the Cons class by just returning the inst var values. You can also define head and tail methods in the EmptyList class, so that now EmptyList and Cons are polymorphic.

Added: Ok, just for fun, an implementation that uses blocks and arrays, functional style:

| empty cons head tail test |
empty := [nil].
cons := [:i :j :value :old | [Array with:i with:j with:value with:old]].
head := [:list | list value ifNotNil: [:v | v copyFrom:1 to:3]].
tail := [:list | list value at: 4].

test := cons value: 1 value: 1 value: 'Hi' value: (cons value: 1 value: 2 value: 'Ho' value: empty).
"Print each one"
head value: test.
head value: (tail value: test).
head value: (tail value: (tail value: test)).

HTH

Andrés Fortier
  • 1,705
  • 11
  • 12
  • 1
    I don't think this is helpful. There is no need to actually emulate a list data structure (see my answer). – codefrau Apr 10 '13 at 17:19