[eggPlant Functional] SenseTalk Objects Tutorial -- Banking

Due to the length of this tutorial, it is also available below as an attachment in rtf format for convenient downloading. [Note: Updated June 2008 – originally posted May 2005]

SenseTalk Objects Tutorial � Banking

To present a practical example of the use of objects in SenseTalk, this article will lead you through the creation of some objects representing bank customers and their bank accounts. To make the concepts clear, the example objects are intentionally simplified, with many details omitted that would be needed in a real banking system. The basic ideas and techniques presented here should be useful in many different situations, however.

Elements of the Banking System

To begin with, let’s take a look at what the elements of our simple banking system will be. A bank customer will have a name, and also an ID number (so we can properly distinguish one customer from another even if two customers happen to have the same name).

In addition, of course, each customer may have one or more bank accounts of different types. So a customer will have properties like this:

BankCustomer
[list] name
id number
accounts[/list:u]
For each account, in turn, we will need to know what type it is (checking, savings, etc.), what the current balance in that account is, and we should also keep track of the various transactions that have occurred in that account such as deposits, withdrawals, and so forth. So an account’s properties will look like this:

BankAccount
[list] type
balance
transactions[/list:u]
Starting Simply

Now that we know something about the properties of these two different types of objects, let’s look at how to implement our simple banking system in SenseTalk. To best understand the material presented here, it is strongly recommended that you follow along and create the scripts while reading (it’s okay to copy and paste if you don’t feel like typing everything, but being able to run the scripts as they grow, and to experiment a little on your own will be very helpful).

For this exercise, begin by creating a script called BankTest. This will be our main script for trying out the banking system and making sure that everything works the way we want. We’ll start out very simply, and build things up as we go. Enter the following in your BankTest script:

put (name:"John Doe", id:1234) into customer
put customer

The first line creates a simple object (often just called a “property list” in this form) containing two properties and stores it in the variable customer. The second line displays our customer object, to verify that everything is working as we expect up to this point.

Quite often, simple objects of this sort are all that are needed. If your objects are simply a collection of values (properties) and don’t require any special behaviors that will be shared by a number of objects in the system, then sticking with simple property lists may be the best approach.

Using Another Object

On the other hand, property lists with built-in behaviors can be very powerful. An object can have both properties and behaviors, and can borrow some or all of its behaviors from other objects (called its “helpers”). Modify the first line of the script to say:

put a new BankCustomer with (name:"John Doe", id:1234) into customer

If you try to run the script now, it will generate an error, something like this:
Looking for an object or script identifier, got ‘BankCustomer’

This is because we haven’t defined a BankCustomer object yet. That’s easily remedied: simply create a new empty script called BankCustomer and save it, and you’ll find that your script should now run without error.

There are a couple of things to note about this. One is that SenseTalk considers a script file (the new BankCustomer script in this case) to be an object. A script, like other SenseTalk objects, can have both properties and behaviors, as we’ll see a little later.

The second thing you may have noticed, if you ran your modified BankTest script, is that it behaved exactly like the original script – the “new BankCustomer” object now stored in the customer variable appears to be identical to the simple property list created by the first script.

In fact, there is a subtle difference, which you can see if you add this line at the end of the BankTest script and run it again:

put customer's helpers

You can see that our customer object now has a helper – namely the BankCustomer script object. To understand the way SenseTalk objects work, it’s important to realize that customer and BankCustomer are two distinct objects.

When we asked for “a new BankCustomer” what we got was not the BankCustomer object, but an entirely new and separate object with its own properties (name and id). We may say that customer “is a BankCustomer” because it inherits behaviors from the BankCustomer object. This can be a convenient way to think about customer, but keep in mind that they are really separate objects. An object like BankCustomer that is used mainly for making new objects is sometimes called a “prototype object”.

Adding Some Behavior

Currently, of course, BankCustomer is a completely empty script. Let’s change that. Enter the following in the BankCustomer script and save it:

to handle asText
	return "Customer id " & my id & ": " & my name & ", Assets: $" & my wealth
end asText

Now run the BankTest script again. This time the display of customer looks different. This is because SenseTalk allows an object to control the way it appears, by sending it an “asText” message – if the object handles the message and returns a value, that value is used as the text representation of the object.

Now, our customer object doesn’t know how to handle the asText message itself. In fact, customer doesn’t have any handlers or even a script at all. But it does have a very powerful “secret weapon” – it has a helper, namely the BankCustomer object. Whenever a message is sent to an object and it doesn’t have a handler for that message, the message is passed along to each of the object’s helpers in turn, to see if any of them can help it out.

You’ll notice that BankCustomer’s asText handler returns a text string made by combining several values including “my id” and “my name” to insert the id and name into the returned value. Something very important is going on here. When BankCustomer received the asText message while acting as customer’s helper, the values that were inserted into the return string were not BankCustomer’s id and name, but those properties of customer!

Whenever an object is helping another, it “assumes the identity” of the object it is helping, so “me” and “my” in that case refer to the object being helped. This makes it easy to write scripts in a very natural way that will work both for the object itself, and for any objects that it may be helping.

The asText return value also refers to “my wealth” but our customer object doesn’t have a wealth property. We will add a behavior to BankCustomer to calculate this value in another handler. Enter this in BankCustomer, either before or after the asText handler (the order of handlers is not significant):

to handle wealth -- calculate my total assets
	set total to zero
	repeat with each account in values of my accounts
		add account's balance to total
	end repeat
	return total
end wealth

Running the BankTest script now will show that John Doe has “Assets: $0”. In order to give John Doe some money, he’ll need to have an account to store it in, so let’s turn our attention now to creating some accounts.

Setting Up Accounts

Add these lines to your BankTest script:

set checking to new BankAccount with (type:"Checking")
put checking

Of course, “BankAccount” hasn’t been defined yet, so create a new script called BankAccount and enter the following:

properties
	balance: 0,
	type: "UNDEFINED",
end properties

This defines two initial properties for the BankAccount object. Running the BankTest script will now display (balance:“0”, type:“Checking”). There are a couple of new things going on here, so let’s take a look at what’s happening.

When we created our checking object by setting it to be a “new BankAccount”, the new object was assigned a copy of the properties of the prototype object (BankAccount). So checking was set to have a balance property with a value of 0. It would also have had its type property set to “UNDEFINED”, except that in this case we specified a value of “Checking” for the type property when the checking object was created, so that value took priority.

Now, it might be nice if the checking object showed something a little friendlier when we display it. We’ve seen how to do that using an asText handler. For very simple cases where the text doesn’t need to change, an object can be given an asText property and its value will be displayed. For our BankAccount objects we’d like something a bit more dynamic but still fairly simple, so we’ll use an asTextFormat property instead. Add this property to the BankAccount script so that it reads:

properties
	balance: 0,
	type: "UNDEFINED",
	asTextFormat: "[[my type]] Account: $[[my balance]]"
end properties

If you run the BankTest script now you should see “Checking Account: $0”. The asTextFormat property here provides a merge template that will be used when displaying our bank accounts as text. The expressions in double square brackets are evaluated each time the object is displayed to produce the output (see Chapter 11 of the SenseTalk Reference manual for more details on the merge function).

Using Accounts

Now let’s see what it takes to assign an account to our customer and use it. Add the following at the end of the BankTest script:

put checking into customer.accounts.Checking

put new BankAccount with (type:"Savings", balance:100) \
		into customer.accounts.Savings

put customer

The first line here puts our checking object into the “Checking” property of the “accounts” property of our customer. Since customer doesn’t have an accounts property, one is created and turned into a property list so that its Checking property can be set to our checking object.

The second line creates another bank account of type “Savings” with an initial balance of 100 dollars, and assigns it to the Savings property of customer.accounts all in one statement. Running the script, you should now see that customer has total assets of $100.

To be truly useful, of course, we need some way to make deposits and withdrawals in our accounts, so add the following code to the BankAccount script (either before or after the properties declarations):

to deposit amount
	add amount to my balance
end deposit

to withdraw amount
	subtract amount from my balance
end withdraw

Now we can do things like this in the BankTest script (try it):

send deposit 500 to customer.accounts.Checking
send withdraw 20 to customer.accounts.Savings

Displaying the customer now should show a total asset amount that reflects the transactions that have taken place. The send command is used here to send a “deposit 500” or “withdraw 20” message to a particular account object. Here is an alternate syntax you can use for sending messages like these if you prefer (in this case to move $150 from the Checking account to the Savings account):

(customer.accounts.Checking).withdraw 150
(customer.accounts.Savings).deposit 150

Recording Transactions

The only thing that remains to be done for our simplistic banking system is to maintain a log of transactions that occur in each account. We can do this by adding a few statements to the BankAccount script to create and update this log.

To start a transaction log for each new account as soon as it is created, add this handler to BankAccount:

to initialize
	insert "StartingBalance" && my balance into my transactions
end initialize

Whenever an object is created using a “new” expression (such as “set checking to new BankAccount”), an “initialize” message is sent to the new object. The initialize message is sent after the object is created (by copying BankAccount’s properties plus any given properties and setting BankAccount as a helper of the new object). So although the checking object doesn’t have a handler for this message, its new helper (BankAccount) will handle the message and initialize its transactions property.

This will start each account object off with a transactions property that lists the initial balance of the account when it was created. The insert command makes the transactions property a list, creating it in this case since it doesn’t already exist.

Remember that when an object is acting as a helper, “me” or “my” refers to the object being helped. Because the object’s initial properties have already been set, the “StartingBalance” entry in the log will show the initial value of the balance property if one was given.

Adding similar insert commands to the deposit and withdraw handlers, our final BankAccount script looks like this:

properties
	balance: 0,
	type: "UNDEFINED",
	asTextFormat: "[[my type]] Account: $[[my balance]]"
end properties

to initialize
	insert "StartingBalance" && my balance into my transactions
end initialize

to deposit amount
	add amount to my balance
	insert "Deposit" && amount after my transactions
end deposit

to withdraw amount
	subtract amount from my balance
	insert "Withdraw" && amount after my transactions
end withdraw

To see entries from the transaction logs, you might try some commands like these in the BankTest script:

put customer.accounts.Checking.transactions
put customer.accounts.Savings.transactions joined by return

put each item of customer.accounts.Checking.transactions where each begins with "Deposit"

Finally, to get a more complete look at our customer’s accounts, add the following handler in the BankCustomer script:

to displayReport
	put me
	repeat with each account in values of my accounts
		put "   " & account
	end repeat
end displayReport

This can be called at the end of the BankTest script like this:

send displayReport to customer

Wrapping Up

This only scratches the surface of what can be done with SenseTalk objects. Hopefully the ideas and techniques presented here will be useful in your own projects.

We touched on a lot of important points, including:

  • the use of simple property lists when specialized behaviors are not needed;
  • the use of “asText” handlers and “asTextFormat” properties to provide a text representation of an object;
  • the idea that an object can be helped by other objects and can serve as a helper to other objects;
  • the distinction between an object and its helpers;
  • the use of “me” and “my” in the script of an object that may be serving as a helper;
  • the use of an “initialize” handler in a prototype object to set additional properties of a new object;
  • sending messages to an object with the “send” command;
  • handlers for performing actions on an object (deposit and withdraw), for accessing information about an object (wealth), and for displaying information (displayReport);
  • object properties that hold simple values (name, id, balance), lists of values (transactions), or other objects (accounts);
  • and (perhaps indirectly) the fact that a property list is an object that doesn’t happen to have any behaviors, that a script is an object that often doesn’t have any properties but may have multiple behaviors in the form of message handlers.

Once the ideas presented here begin to soak in and make sense, you may find it valuable to read (or re-read) chapters 8, 9, and 10 of the SenseTalk Reference manual, which provide more details on the use of lists, property lists, objects, helpers, and messages in SenseTalk.

Thank you for this nice explanation on objects.
Is it also possible to create a more than one object with handlers and properties in one script?

No. A script is an object. To define another object with different handlers, create another script.

Of course, when a script is running it can create many objects of many different types using a new expression, but I don’t think that’s what you meant.

1 Like