You are currently browsing the category archive for the 'Uncategorized' category.

I’m working through the tutorial and making videos of the exercises (part of the incentive is to get an idea of how long things should take). You can watch the videos here:

  1. Installing ‘Seaside One-Click Experience’ (1 min, 40 sec)
  2. ‘Hello World’ in Seaside (4:40)
  3. Exploring Seaside Examples (4:35)

I think it is neat to have a video showing that it takes just a few minutes to have a “Hello World” application implemented.

Come to Orlando in October for a Seaside Tutorial at OOPSLA.

Jonathan Bush on Fortune, referring to Epic: “The Cleveland Clinic has software that they had to pay $200 million to get. It was written in MUMPS in 1974. There is nobody left alive who can write MUMPS any more. That’s the model … the curve of innovation, the disruptive technology engine in healthcare is broken.” (hat tip to HIStalk)

My first programming job was in 1974 building a MUMPS system and I did several years of work on a clinical system in late 1980s and early 1990s. MUMPS is an interesting language, and shares some characteristics with GemStone/S. It is a dynamic language (originally interpreted) and the execution environment includes a built-in database. The database is hierarchal rather than relational, and values put in the database are untyped. While MUMPS does not have true closures, it does have the ability to pass blocks of code and the pattern of passing code to subroutines is common.

I wonder if GemStone/S could be the disruptive technology for healthcare software…

Occasionally there are questions about setting up a SqueakSource repository and the response seems to be that it is most reliable on older versions of Squeak and Seaside. While I certainly agree with the “if it’s not broken, don’t fix it” philosophy, it seems that eventually something will break and fixing it (or enhancing it) will be more difficult if the related pieces are out-of-date.

Also, there are occasional problems with saving to a file system that a transactional database might handle better, and if I’m going to do any work with SqueakSource, I’d rather it be in a current version of my tools.

There is a project to port SqueakSource to Seaside 2.8, but it hasn’t had lots of activity. I’ve spent a few days recently poking at it and have made some progress getting it to run in a current Pharo image with a recent Seaside 2.8 version. Also, I’ve been able to load the code into GemStone and my brief experiments with each platform suggest that it is working in at least a superficial manner.

I’ll start using SqueakSource2 in GemStone and see how things go.

The Seaside One-Click Experience has been updated from a Squeak-based image to a Pharo-based image (now at version 2.8.4). That meant that the screen shots in my tutorial were out-of-date so I’ve spent a day running through the instructions making new pictures. Let me know if you find any problems.

I’m pleased to report that this year’s OOPSLA will include several tutorials with a Smalltalk theme. On Thursday afternoon (tentative schedule) I’ll be sharing “Can you be Rich and Thin? Building Dynamic Web Applications with Seaside.”

Is there a inherent contradiction between a rich client/server application and a thin client? Can a web application provide the control flow that we have come to expect from good desktop applications? Why is it that so many otherwise professionally-developed web sites (including on-line banking, travel reservations, and even the OOPSLA submission system) include warnings like, “Do not save, print or reload this page!” or “Do not use your browser’s <Back> button!”? Has the web really set programming back to the era of GOTO? Is a basic subroutine call too much to ask from your web framework? Why is it that popular web frameworks devote so much to handling object-relational mapping?

While other web frameworks are evolutionary, Seaside has been characterized as revolutionary, even heretical. What makes Seaside different? Is it the funny URLs? Is it continuations? Is it the ability to create reusable domain-specific components? All of the above?

This hands-on tutorial will present Seaside (a free, open source, web framework) and walk through the process of building a pure-objects application (UI to database) using Squeak and GLASS (GemStone, Linux, Apache, Seaside, and Smalltalk). You may bring your own computer or team up with someone else who brought one.

Mark you calendars for October 25-29, 2009.

I’m pleased to report that this year’s OOPSLA will include several tutorials with a Smalltalk theme. On Thursday morning (tentative schedule) I’ll be sharing “Scaling Objects for the Enterprise: What If Thousands of VMs Could Share Terabytes of Objects?

During the past couple decades OOP has become sufficiently mainstream so that we take for granted that our domain model will be manipulated by applications using objects. What has not become common, however, is the use of objects when sharing domain data between instances of the application. Instead, we commonly see things like memcached (which stores bytes from which objects must be created) and relational databases (which store a handful of primitive types from which objects must be created). This process of converting objects to and from some other format slows down not just the development process but the runtime performance as well.

In this Tutorial we will study details of the architecture of GemStone/S, an object database and application server that was introduced at the first OOPSLA in 1986. We will look at techniques used to allow thousands of virtual machines on hundreds of networked hosts to share terabytes of objects using true database semantics (ACID: Atomic, Consistent, Isolated, and Durable). We will see how all memory caching and on-disk storage is of true objects in the same format as used by the application. The presentation will be an in-depth technical discussion of implementation details, not a product demo.

Mark you calendars for October 25-29, 2009.

Following is a screen capture of part of my upcoming vacation back to Fargo:

What is your longest layover between flights?

It looks like they are treating Sunday and Friday as one leg rather than two legs. I do hope they let me take my luggage. (At least it is a change of planes so I don’t have to stay in the plane for 110 hours!)

As has been mentioned earlier, we have a Seaside tutorial available for download. I’ve gotten some good feedback from various people (some of it quite enthusiastic), including Stephan Eggermont from the Netherlands. Stephan recently emailed about his effort to help a neighbor’s 19-year-old son with a programming project assigned as part of his professional education (and has agreed to let me share it here).

It is a 6 month project where the students are supposed to practice software development/methodology. The first 5 months were typical waterfall: requirements gathering, planning, writing use cases, and creating other documents, leaving a month to build the system. As in all proper waterfall processes, they somewhere lost a few weeks, leaving only two weeks to implement the system. As you can imagine, the young man was not very happy about this situation and this is when he came to me.

I asked him what  implementation technology they were supposed to  be using. For better or worse, that decision was left to the students, and there were no pre-build environments available. He has done some previous work with ASP.NET and PHP, but is only fluent in Visual Basic. He had no prior exposure to Smalltalk at all.

I then asked how the projects would be evaluated. He explained that the ultimate evaluation would be by an internal customer from another department with no IT knowledge. Three groups of 1 or 2 students would to show their projects, and one solution will be chosen to be developed further over the summer holidays.

I took a look at the use cases and database design he had written, and explained to him that the fastest way to get an application up and running was probably to use Seaside and the Los Boquitas tutorial. Furthermore, he should switch from waterfall to an iterative approach, driving the planning with a priority of reducing risk and providing value early.

I told him to download the tutorial and try the exercises for a day, and then reevaluate his plans. At the end of the first day, he hadn’t finished all he’d wanted to but came to the conclusion that Seaside would provide the best chance of delivering the system in time. For the next day he planned (and I pushed!) to finish the tutorial to chapter 12.

As a complete Smalltalk beginner, most of his difficulties involved (1) using the Squeak IDE, (2) understanding how collections, blocks, and cascades work, and (3) the difference between class-side and instance-side methods.

The next day, he took at look at using Monticello, and started extending the Los Boquitas application. First, he added a report and an editor for users. A day later he extended and copied the user editor for two different user roles–teachers and students. At this point he started believing that he would be able to deliver the application in time, using the one day plan-develop-evaluate cycle.

With a week or so left on the six month schedule, his teacher announced that the scope of what they should deliver was to be reduced.

Continuing on the development, the young man added some additional domain objects, a report, and an editor, and over the weekend, took some time to clean up the methods. (Using tabs and a standard coding style makes a lot of difference in the readability of the code.)

Next, he started adapting the sidebar links to the current user role. On Monday of the final week, he enhanced the UI so that making connections between various domain objects was done using selects instead of text inputs.

At the end of a week of daily small improvements, he presented the application to his teacher. It took him about two hours to explain image-based persistence and that with GemStone no external database is necessary. The second presentation, to the in-house customer, was an easier sell. (He made one small addition between the first and second presentation: export a tab-delimited list of objects for import into Excel.)

As we expected, his was the only project reasonably close to feature complete. Another group showed a set of web forms in PHP, not yet coupled to the database, and the third group didn’t show anything at all.

The customer now has a week to decide which application has won. I have no doubts on which application will be selected, especially since I’ve instructed the student to keep on making daily improvements, put them on seasidehosting.st and send his teacher and the customer a daily update mail.

Overall, a very good report! Congratulations to the young man (who spent about 5 hours per day for two weeks on the project) and to Stephan for his mentoring.

So, what have you done lately to promote Smalltalk and Seaside? Feel free to share!

I’m pleased to report that this year’s OOPSLA will include several tutorials with a Smalltalk theme. First is “Back to the Future: Programming in Smalltalk,” tentatively scheduled for Monday morning, October 26, 2009 (the official schedule is not yet available, but you can get more information on the conference at http://www.oopsla.org/oopsla2009/). 

If you’ve been hanging around OOPSLA for a while you’ve probably heard some curmudgeon respond to a “new” idea with, “we had that in Lisp 50 years ago” or ”we had that in Smalltalk 30 years ago.” Truth be told, there are a number of elegant ideas that were generally regarded as impractical when they were introduced decades ago, but have become popular over time. These include a GUI, automatic garbage collection, dynamic (“duck”) typing, and (especially) Object Oriented Programming. 

Alan Kay coined the term “object-oriented” and gave us a language (Smalltalk) that is still extremely influential on subsequent languages (for example, Ruby has been described as “Smalltalk with a Perl syntax”). Yet, Smalltalk is ”different” in a number of respects:

  • All values are objects, even integers, booleans, and characters (no boxing/unboxing);
  • Classes and methods are objects (supporting reflection);
  • The language has only five reserved words;
  • All control flow (looping and conditional branching) is done through message sends;
  • Programming is done by sending messages to existing objects; and
  • The base class library can be modified.

Come learn more! The tutorial exercises can be done on a variety of Smalltalks, including a free open source one.

In GemStone/S 32-bit there is a one billion (2^30 – 1) object limit and one of the features of the 64-bit product is that this limit is raised to one trillion (2^40 – 1). As databases become large, an obvious question is, “What types of objects are in my database?” For example, imagine you knew that you had the following objects:


	2522819	#String
	 823195	#AuditEventSet
	 502189	#UpdateEvent
	 445152	#Association
	 433474	#OrderedCollection
	 398370	#SymbolKeyValueDictionary
	 386246	#GsfsIdentitySet
	 385162	#Array
	 160303	#DateTime
	 148078	#CollisionBucket
	 131604	#LhsTimestamp
	 129642	#AuditEventArray
	 128050	#GsMethod
	 124073	#FdbDrugDrugInteractionSet
	 108768	#MedicationSet
	 100789	#KeyValueDictionary

I recently made a tool named 'ScanBackup' that will look at a normal GemStone/S 32-bit backup (made with Repository>>#'fullBackupTo:') and when run from a live version of the database from which the backup was made will give a report of the object names and counts. The code follows in Topaz file-in format:

doit
Object subclass: 'ScanBackup'
  instVarNames: #( file position bytes
                    offset short0 short1 long0
                    long1 long2 long3 classes)
  classVars: #()
  classInstVars: #( classes)
  poolDictionaries: #[]
  inDictionary: UserGlobals
  constraints: #[  ]
  instancesInvariant: false
  isModifiable: false

%

! Remove existing behavior from ScanBackup
doit
ScanBackup removeAllMethods.
ScanBackup class removeAllMethods.
%
! ------------------- Class methods for ScanBackup
category: 'other'
classmethod: ScanBackup
classes

  classes notNil ifTrue: [^classes].
  classes := IntegerKeyValueDictionary new.
  AllUsers do: [:eachUser |
    (System canRead: eachUser) ifTrue: [
      (System canRead: eachUser symbolList) ifTrue: [
        eachUser symbolList do: [:eachSymbolDictionary |
          (System canRead: eachSymbolDictionary) ifTrue: [
            eachSymbolDictionary do: [:eachGlobal |
              (System canRead: eachGlobal) ifTrue: [
                eachGlobal isBehavior ifTrue: [
                  (System canRead: eachGlobal classHistory) ifTrue: [
                    eachGlobal classHistory do: [:eachClass |
                      (System canRead: eachClass) ifTrue: [
                        classes at: eachClass asOop put: eachClass.
                      ].
                    ].
                  ].
                ].
              ].
            ].
          ].
        ].
      ].
    ].
  ].
  ^classes.
%
category: 'other'
classmethod: ScanBackup
scanBackupAtServerPath: aString
"
	ScanBackup scanBackupAtServerPath: '...'.
"
	^self new
		path: aString;
		report.
%
! ------------------- Instance methods for ScanBackup
category: 'other'
method: ScanBackup
byteAt: anInteger

	^bytes at: offset + anInteger.
%
category: 'other'
method: ScanBackup
longAt: anInteger

	^(bytes at: offset + anInteger + long0)
		* 256 + (bytes at: offset + anInteger + long1)
		* 256 + (bytes at: offset + anInteger + long2)
		* 256 + (bytes at: offset + anInteger + long3).
%
category: 'other'
method: ScanBackup
path: aString

	file := GsFile
		open: aString
		mode: 'rb'
		onClient: false.
	position := 0.
	bytes := ByteArray new: 1024 * 1024.
	offset := 0.
	classes := IntegerKeyValueDictionary new.
	[
		self readFile.
	] ensure: [
		file close.
	].
%
category: 'other'
method: ScanBackup
physicalRecordHeaderSize

	^28.
%
category: 'other'
method: ScanBackup
read: anInteger

	file next: anInteger into: bytes.
	position := file position.
	offset := 0.
%
category: 'other'
method: ScanBackup
readDataRecord

	| numObjs |
	numObjs := self shortAt: 3.
	offset := offset + 8.
	1 to: numObjs do: [:i | self readObject].
%
category: 'other'
method: ScanBackup
readFile

	self readFirstPhysicalRecord.
	[
		file atEnd not.
	] whileTrue: [
		self readPhysicalRecord.
		System abortTransaction.	"Avoid holding a commit record"
	].
%
category: 'other'
method: ScanBackup
readFirstPhysicalRecord

	| localHeaderSize physicalRecordKind logicalRecordKind
		logicalRecordSize numLogicalRecords physicalRecordSize |
	localHeaderSize := 28.
	self read: self physicalRecordHeaderSize + localHeaderSize.
	offset := 0.
	(physicalRecordKind := self byteAt: 1) = 18
		ifFalse: [self error: 'Unrecognized record type!'].
	self setSwizzle.
	(logicalRecordKind := self shortAt: 39) = 12 "LOG_BACKUP_ROOT_RECORD"
		ifFalse: [self error: 'Unexpected record!'].
	logicalRecordSize := self shortAt: 41.
	numLogicalRecords := self shortAt: 7.
	physicalRecordSize := (self shortAt: 5) * 1024.
	self read: logicalRecordSize - localHeaderSize.
	self read: physicalRecordSize - logicalRecordSize - self physicalRecordHeaderSize.
	offset := 0.
	2 to: numLogicalRecords do: [:i |
		self readLogicalRecord.
	].
%
category: 'other'
method: ScanBackup
readLogicalRecord

	| recordKind recordSize oldOffset |
	recordKind := self shortAt: 11.
	recordKind = 12 "LOG_BACKUP_ROOT_RECORD"
		ifTrue: [self error: 'Should have already processed this record!'].
	recordSize := self shortAt: 13.
	oldOffset := offset.
	offset := offset + 16. "header size"
	recordKind = 03 "LOG_DATA_RECORD" ifTrue: [self readDataRecord] ifFalse: [
	recordKind = 13 "LOG_BACKUP_EOF_RECORD" ifTrue: [] ifFalse: [
	recordKind = 14 "LOG_BACKUP_START_CHECKPOINT" ifTrue: [] ifFalse: [
	recordKind = 15 "LOG_BACKUP_END_CHECKPOINT" ifTrue: [] ifFalse: [

	'What kind of record is this?' halt.
	]]]].
	offset := oldOffset + recordSize.
%
category: 'other'
method: ScanBackup
readObject

	| objID objClass physSize count |
	objID := self longAt: 1.
	objClass := self longAt: 5.
	physSize := self shortAt: 19.
	offset := offset + physSize.
	count := classes
		at: objClass
		ifAbsentPut: [0].
	classes
		at: objClass
		put: count + 1.
%
category: 'other'
method: ScanBackup
readPhysicalRecord

	| pageKind numLogicalRecords recordSize |
	self read: self physicalRecordHeaderSize.
	(pageKind := self byteAt: 1) = 18
		ifFalse: [self error: 'Unrecognized record type!'].
	numLogicalRecords := self shortAt: 7.
	recordSize := (self shortAt: 5) * 1024.
	self read: recordSize - self physicalRecordHeaderSize.
	1 to: numLogicalRecords do: [:i |
		self readLogicalRecord.
	].
%
category: 'other'
method: ScanBackup
report

	| stream list |
	stream := WriteStream on: String new.
	list := OrderedCollection new.
	classes keysAndValuesDo: [:key :value | list add: key -> value].
	list := list asSortedCollection: [:a :b | a value > b value].
	1 to: 200 do: [:i |
		| classOop count theClass |
		classOop := (list at: i) key.
		count := (list at: i) value.
		theClass := self class classes at: classOop ifAbsent: [nil].
		theClass notNil ifTrue: [
			i printOn: stream.
			stream tab.
			count printOn: stream.
			stream tab.
			theClass name printOn: stream.
			stream cr.
		].
	].
	^stream contents.
%
category: 'other'
method: ScanBackup
setSwizzle

	| swizzle |
	swizzle := (bytes copyFrom: 49 to: 54) asArray.
	swizzle = #(0 1 2 3 0 1) ifTrue: [
		short0 := 1.
		short1 := 0.
		long0 := 3.
		long1 := 2.
		long2 := 1.
		long3 := 0.
		^self.
	].
	swizzle = #(3 2 1 0 1 0) ifTrue: [
		short0 := 0.
		short1 := 1.
		long0 := 0.
		long1 := 1.
		long2 := 2.
		long3 := 3.
		^self.
	].
	self error: 'Unexpected file type!'.
%
category: 'other'
method: ScanBackup
shortAt: anInteger

	^(bytes at: offset + anInteger + short0)
		* 256 + (bytes at: offset + anInteger + short1).
%