How can I create a 2D array and methods to fetch/store

If you have any questions, remarks, ... if you need help... its here...

How can I create a 2D array and methods to fetch/store

Postby bobgillies » 30 Jan 2017 20:14

What I'm trying to do is create a square matrix and a method that takes a row and column parameters on the stack to index the matrix to check, fetch and store values. Thanks in advance.
bobgillies
 
Posts: 60
Joined: 24 Jan 2017 06:26

Re: How can I create a 2D array and methods to fetch/store

Postby Franck » 30 Jan 2017 20:58

Here is a small Matrix class with #at(i, j) and #put(i, j, x)

Code: Select all
Object Class new: Matrix(n, m, mutable values)

Matrix method: initialize( n m -- )
   n := n
   m := m
   ArrayBuffer newWith( n m * , 0 ) := values
;

Matrix method: at( i j -- x )
   i 1- @n * j + @values at ;

Matrix method: put( i j x -- )
   @values put( i 1- @n * j + , x ) ;

Matrix classMethod: id( n )
| i m |
   n n self new dup ->m
   n loop: i [ m put( i, i, 1 ) ]
;

Matrix method: <<
| i j |
   @n loop: i [
      "\n" <<
      @m loop: j [ i j self at << " "<< ]
      ]
;



Examples :

Code: Select all
3 4 Matrix new dup put( 2, 2, 1.3) .s
[1] (Matrix)
0 0 0 0
0 1.3 0 0
0 0 0 0


Code: Select all
5 Matrix id .s
[1] (Matrix)
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1

>dup put(3, 5, 1.3) .s
[1] (Matrix)
1 0 0 0 0
0 1 0 0 0
0 0 1 0 1.3
0 0 0 1 0
0 0 0 0 1
ok


Hope it will help,

Franck
Franck
 
Posts: 140
Joined: 29 Oct 2014 19:01

Re: How can I create a 2D array and methods to fetch/store

Postby bobgillies » 30 Jan 2017 21:07

:D
bobgillies
 
Posts: 60
Joined: 24 Jan 2017 06:26

Re: How can I create a 2D array and methods to fetch/store

Postby bobgillies » 02 Feb 2017 06:34

I have some questions. It's in the method that initialzes an object by taking two numbers off the stack (the matrix row and columns).

n : = n
m : = m

1. In laymen's terms, what's the assignment operator " := " actually doing from an deeper OOP perspective?

2. Both sides of the ":=" operator are the same symbol. What's role does the parameter on the left side of the ":=" play in relation to the right side of the ":="? I'm confused because the symbol's on each side are the same.

2. When an initializing method runs how does one check if the top stack value is an integer?
bobgillies
 
Posts: 60
Joined: 24 Jan 2017 06:26

Re: How can I create a 2D array and methods to fetch/store

Postby Franck » 02 Feb 2017 10:36

Well, this is my fault, I used the same names and this adds confusion.

There are 2 things :

1) In Oforth, "(" and ")" are not comments like in Forth.
They are really used to declare local parameters into functions or methods.
{ and } are used to declare JSON objects.

In Oforth
Code: Select all
: t( a b -- n )
   a b + ;


is the same as in Forth :
Code: Select all
: t { a b -- n }
   a b + ;


So, into #initialize, 2 parameters, n and m, are declared and, when #initialize runs, 2 cells are taken from the stack and used as parameters values.

2) := and @
Those words are parsing words (like TO). They can only be used into methods as they need an object to work.
They read the next name into the input buffer and generate code.

:= generates code to pop a cell from the stack and store it into the object field with name read into the input buffer.

@ generates code to push on the stack the value of the field with name read into the input buffer on the stack.

So
Code: Select all
n := n

1) pushes the n parameter declared for this method on the stack
2) Store this value into the Matrix field with name n.

Without declaring parameters, #initialize could be written like this :

Code: Select all
Matrix method: initialize     \ n m --
   2dup := m := n
   * 0 ArrayBuffer newWith := values
;



3) Checks
If you want to check if an object is of a particular class, you can use #isA
This method returns true if an object is of certain type, false otherwise.

So you can use (not checked, I don't have Oforth available right now) :

Code: Select all
Matrix method: initialize( n m -- )
   n Integer over isA ifFalse: [ "Line is not an integer" abort ] := n
   m Integer over isA ifFalse: [ "Cols is not an integer" abort ] := m
   ArrayBuffer newWith( n m * , 0 ) := values
;


Or you can use #class : this returns an object class. For instance :
Code: Select all
2 class Integer =     \ true
2.1 class Integer =   \ false




In fact, I should have used "lines" and "cols" into the Matrix definition for numbers of lines and columns.
Like this :

Code: Select all
Object Class new: Matrix( lines, cols, mutable values )


To go deeper into Oforth OOP mecanisms, this creates a new class named Matrix, subclass of Object class.
A class is a word. When interpreted, this object is just pushed on the stack.

A class allows to define an object type (here Matrix) and the fields that an object of type Matrix will have.
This is like a C structure. Here, a matrix object will have :
- a field named "lines" : number of lines
- a field named "cols" : number of columns
- a files named "values" : the matrix values.

Each object of type Matrix will have those fields available, where you can store any value you like.
Of course, each matrix object has those 3 fields but its own values for these fields.

After a class is created, you can use it to :
- Create method implementations a matrix will respond to (#intialize, #at, #put, ...).
- Create Matrix objects on the stack, using #new method.

Every time you call #new to create a new object of a particular class, #initialize method is called.
(if this method is not declared for a class, nothing is done and all fields are initialized to null value).

After this method is invoked, the Matrix object is initialized.
And when the #new method returns, this object is available on the stack.


Code: Select all
Matrix method: at( i j -- x )
   i 1- @lines * j +  @values at ;


This creates the #at method implementation for Matrix class.
It calculates the index into the array of values and fetch it.
@n pushes on the stack the field value "lines" of the matrix we are using (the receiver).

When a method is invoked, it removes the top of the stack and store it into the "self" value.

Code: Select all
Matrix method: put( i j x -- )
   i 1- @n * j +  x  @values put ;


Same as #at, but to store a value into the array of the matrix values.


Code: Select all
Matrix classMethod: id( n -- aMatrix )
| i |
   n sq self new
   n loop: i [ dup put( i, i, 1 ) ] ;


This is a class method. Unlike methods that will work on Matrix objects, class methods work directly on Matrix class.
This method allows to create an identity matrix.
It calls #new to create a Matrix of n*n, then loop to store 1 on the diagonal.
Here, self (the receiver) is the Matrix class itself.


Code: Select all
Matrix method: <<
| i j |
   @n << " x " << @m << "\n" <<
   @n loop: i [ @m loop: j [ i j self at << " "<< ] "\n" << ]
;


This creates a method to print a matrix. It is called by #.s, so the matrix can be printed.
Into this method, self is the matrix, so using #self will push the matrix we are working on on the stack.


Franck
Franck
 
Posts: 140
Joined: 29 Oct 2014 19:01

mutability, inheritance and polymorphism

Postby bobgillies » 03 Feb 2017 17:49

Thank you again Franck for the in depth overview of OOP within oforth. Would it be fair for me to say then:

This is a creation of a class of objects named "Matrix" with three data field types (immutable, immutable, and mutable) for it's structure.

This method "initialize" and it's definition prepares the "Matrix" data fields.

These other methods and their definitions are the protocol to operate on the "Matrix".

A couple of question, sir.

1. Can I force immutable data field types to be modified at a later time? An example of how that would be dangerous.

2 . Are adding extra data fields allowed after a method initialization? An example of how that would be dangerous.

Also, would you please give me examples of inheritance and polymorphism on a Matrix so I may get a better understanding in oforth of those OOP concepts, too.

Thanks in advance,
Bob
bobgillies
 
Posts: 60
Joined: 24 Jan 2017 06:26

Re: How can I create a 2D array and methods to fetch/store

Postby Franck » 05 Feb 2017 20:19

You are righ for your 3 comments.

This is a creation of a class named Matrix with 3 data types (immutable, immutable, and mutable) for it's structure.
As at least one of the fields is mutable, a Matrix object is mutable.

#initialize and it's definition prepares the Matrix data fields.
This is the only place where you can set immutable fields.
After that, immutable fields are no more updatable, whatever you do (checked at runtime).

Other methods and their definitions are the protocol to operate on the "Matrix".


1. Can I force immutable data field types to be modified at a later time? An example of how that would be dangerous.

No, you can't. Let's try :

Code: Select all
Matrix method: lets_try
   10 := n ;

>5 Matrix id
ok
>lets_try
[stdin:1:8] Exception : Immutable rule violation
ok


When you create a Matrix object, data space allocated during initialisation and the matrix structure are highly related to number of lines and columns given during initialization.
THe way the protocol is written, updating those 2 values after initialisation (like into #lets_try method) should not be permitted after the matrix object is created.
That's why the two first fields are set as immutable.

Of course, you could declare those fields as mutable, but you will need to handle this into the Matrix protocol (recreate a new ArrayBuffer for values ?, ...).

Also, mutables objects (like those Matrix objects) have restrictions because Oforth is designed for multi-threading.
If a mutable object could be shared by tasks running in parallel, this could be dangerous.

For instance, a mutable object can't be the value of a constant. Let's try :

Code: Select all
5 Matrix id const: MY_MATRIX
[stdin:1:28] ExRuntime : Immutable check error for <
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1 >
ok



2 Are adding extra data fields allowed after a method initialization? An example of how that would be dangerous.

No, you can't add fields after initialization. You can't even add fields after the class creation.
Once you write :

Code: Select all
Object Class new: Matrix( lines, cols, mutable values )


Matrix objects will have 3 fields and this will never change for all the Oforth session.

If you could adds fields after the class is created, what will happens with Matrix objects created before adding those fields ? How many fields they should have ?
Also, in what circomstances adding fields to an existing class would be required ?
This would be like adding fields to a C structures after structures have been created. Behaivor would be undefined.

But, of course, if you create a subclass, you can add new fields to the subclass. For instance :

Code: Select all
Matrix Class new: Matrix2D( myfield )


A Matrix2D object will have 4 data fields (lines, cols, values and myfield)



3 Also, would you please give me examples of inheritance and polymorphism on a Matrix so I may get a better understanding in oforth of those OOP concepts, too.


Polymorphism is general, not only for Matrix and its subclasses.
For instance #at and #put are methods already defined for other types (ArrayBuffer, ...).
Every class can implement every method, whatever its position into the hierachy. This is a big difference with languages like C++ or Jave.
And this is why Oforth OOP is close to Smalltalk OOP.

When you create a subclass of a class :
1) The subclass inherit of all fields of the parent class (and can adds its own fields).
2) The objects of type of the subclass inherit of all the protocol of the parent class (and other methods can be added)

So, if you create a subclass, its objects will respond to all methods declared for the parent class (and parent of the parent, ... ).

For instance, Matrix inherit of all methods declared at the Object level :

Code: Select all
5 Matrix id print


You can call a method declared at the upper level (using super instead of self) but, by default, you can't redefine a method declared at the upper level. For instance :
Code: Select all
>Matrix method: print "My print" ;
[console:1:20] Exception : Can't redefine non virtual method <#print>
ok


This is because, most on the time, when you define a method at the parent level, this method should apply at le child class level.
Otherwise, why would you create a subclass ?

But, sometimes, it is required to redefine a method at the child level, that will call the parent method. To do this, you must declared the method at the parent level as virtual.
A virtual method can be redefined by child classes. For instance :

Code: Select all
Object Class new: Matrix(n, m, mutable values)

Matrix virtual: initialize( n m -- )
   n := n
   m := m
   ArrayBuffer newWith( n m * , 0 ) := values
;

Matrix Class new: Matrix2D

Matrix2D method: initialize   \ n --
   dup super initialize ;

5 Matrix2D new .s
[1] (Matrix2D)
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
ok


Franck
Franck
 
Posts: 140
Joined: 29 Oct 2014 19:01

Re: How can I create a 2D array and methods to fetch/store

Postby bobgillies » 05 Feb 2017 21:21

I thank you again Franck for opening the door for a glimpse of the OOP toolset. Your OOP examples clarify what I needed to go forth trying out some fun challenges contained in an algorithm textbook of mine . :)
bobgillies
 
Posts: 60
Joined: 24 Jan 2017 06:26

Re: How can I create a 2D array and methods to fetch/store

Postby Franck » 05 Feb 2017 23:14

You're welcome :)
Franck
 
Posts: 140
Joined: 29 Oct 2014 19:01

Re: How can I create a 2D array and methods to fetch/store

Postby Franck » 05 Feb 2017 23:45

Reading my post, I realized that a part was confusing.

You need "super" only when you want to call a method at upper level that is redefined in the child class.
If no redefined, "self" will do to job.

For instance, using the example Matrix2D, you can write, in order to sum the diagonal :

Code: Select all
Matrix2D method: foo
| i |
   0 @n loop: i [ i i self at + ]
;


No need of "super" here : #at is defined at Matrix level, so a Matrix2D object will answer to it.
"super" is needed when we want to call the method at the upper level *and* not the one defined into the child.

Franck
Franck
 
Posts: 140
Joined: 29 Oct 2014 19:01

Next

Return to General

Who is online

Users browsing this forum: No registered users and 1 guest

cron