Immutable Interface

Research: This page describes research about Eiffel, not the actual language specification.

Introduction

Eiffel Base 2 relies on partial (see below) immutable interfaces to design better abstractions and to allow a better use of the library. This page sketch a solution to allow full immutable interface designs.

Definition

An interface is immutable if it exports only queries. The routines exported to no classes are not included in the class interface. Note that an immutable class has an immutable interface and more queries must be pure, i.e. with no side-effect.

Issue

Immutable interfaces are not conceivable in the current state of the Eiffel Standard. Indeed, all classes inherit of ANY. However ANY has not an immutable interface, it exports some commands. Therefore no immutable interface is conceivable.

ANY class interface (queries are omitted):

class interface
	ANY
 
feature -- Duplication
 
	copy (other: like Current)
			-- Update current object using fields of object attached
			-- to `other', so as to yield equal objects.
		require
			type_identity: same_type (other)
		ensure
			is_equal: Current ~ other
 
	frozen deep_copy (other: like Current)
			-- Effect equivalent to that of:
			--		copy (`other' . deep_twin)
		ensure
			deep_equal: deep_equal (Current, other)
 
feature -- Basic Operations
 
	default_rescue
			-- Process exception for routines with no Rescue clause.
			-- (Default: do nothing.)
 
	frozen do_nothing
			-- Execute a null action.
 
feature -- Output
 
	print (o: detachable ANY)
			-- Write terse external representation of `o'
			-- on standard output.
 
end

Make ANY as immutable interface

Export commands to no classes

  • The command 'default_rescue' allows to change an internal behavior. This command can be exported to no classes.
  • The command 'do_nothing' is a procedure specially interesting for agent uses.

A qualified call of 'do_nothing' on any objects is not useful. An interesting use can be:

class
	EXAMPLE
 
feature
 
	do_something
		do
			do_something_with (agent {A}.do_nothing)
		end
 
	do_something_with (a: PROCEDURE [ANY, TUPLE [A]])
		do end
 
end

However the contravariance (concept not introduced yet) of the second generic parameter of PROCEDURE can remove this use:

class
	EXAMPLE
 
feature
 
	do_something
		do
			do_something_with (agent do_nothing)
		end
 
	do_something_with (a: PROCEDURE [ANY, TUPLE [A]])
		do end

Therefore the command 'do_nothing' can be exported to no classes.

  • The command 'print' could be called only with unqualified calls. It can be too exported to no classes.
  • The commands 'copy' and 'deep_copy' are probably used mainly through the queries 'twin' and respectively 'deep_twin'.

Export this commands to no classes seems have no important disturbance. It is always possible to export its in descendants.

However, we can easily imagine that 'twin' and 'deep_twin' create a new object and then apply 'copy' and respectively 'deep_copy'. This process does not follow the Eiffel creation philosophy. Maybe it is an opportunity to correct it and then to write the queries 'twin' and 'deep_twin' with Eiffel code.

A solution would be to make both commands as creation commands.

class
	ANY
 
create
	copy,
	deep_copy,
	default_create
 
feature {NONE}-- Initialization
 
	copy (other: like Current)
			-- Update current object using fields of object attached
			-- to `other', so as to yield equal objects.
		require
			type_identity: same_type (other)
		external
			"built_in"
		ensure
			is_equal: Current ~ other
		end
 
	-- ...
 
feature -- Duplication
 
	frozen twin: like Current
			-- New object equal to `Current'
			-- `twin' calls `copy'; to change copying/twinning semantics, redefine `copy'.
		do
			create Result.copy (Current)
		ensure
			is_equal: Result ~ Current
		end
 
	-- ...
 
end

The both commands can be exported to no classes and two queries can be writed in Eiffel code. However the developer must declare the creation commands for each effective class.

To remove this issue we can introduce the inherited creation command mechanism.

Inherited Creation Command

A simple addition to Eiffel could be to allow the create clause in deferred classes. The creation commands declared in deferred classes would be inherited creation commands, i.e. creation commands implicitly declared in each descendants.

If an effective class declares no new creation commands then all inherited creation commands and the command 'default_create' are creation commands. If an effective class declares new creation commands then all inherited creation commands and new ones are creation commands. An inherited creation command can be declared again in an effective class. This possibility allows then all inherited commands as creation commands without 'default_create' (except if 'default_create' is an inherited creation command).

deferred class
	A
 
create -- inherited
	make
 
feature -- Initialization
 
	make (a_n: INTEGER)
		deferred end
 
end
class
	B
 
inherit
	A
 
feature
	-- 'make' and 'default_create' are creation procedures
 
end
class
	C
 
inherit
	A
 
create
	make
 
feature
	-- 'make' is a creation procedure
 
end


This addition does not affect existing code. The compatibility is preserved. And more it can be useful in constrained generic context.

ANY and inherited creation commands

The mechanism described in the previous section is not usable on ANY. Indeed, ANY is an effective class. However is it very useful to allow the creation of ANY instance?

I have not examples of use of ANY instances. Maybe it is more relevant to make ANY as deferred class and then to use the inherited creation command mechanism.

deferred class
	ANY
 
create -- inherited creation commands
	copy,
	deep_copy
 
feature
	-- ...
 
end


Conclusion

Export all ANY class's commands allows the design of full immutable interfaces.

In addition, the inherited creation command concept is a minimal mechanism compatible with existing software. It introduces no new keyword and allows to write two queries of ANY in Eiffel code.