Enums in Eiffel

Revision as of 15:48, 3 May 2007 by Paulb (Talk | contribs) (An Example)

Construction.png Not Ready for Review: This Page is Under Development!

Preface

Something pondered for years was the question regarding why Eiffel has never embraced Enum types or a variant more in style with the Eiffel paradigm. There are a number of comments from numerous developers regarding why Enums are "Bad". Most languages exhibit bad ideals and some more than others. The congisen is that if you give a developer a tool to abuse it will be abused and sometimes by the seasoned developers. Generally, seasoned developers have a grasp of the dangers of abusing aspects of a language to gain performance or micro design. However lessons are learned from those who know better from those that know less. As such bad programming practices creep down the chain until it become a common convention.

In this document I'll outline the pontetial dangers and the oddities found commonly with the "Enum" type and attempt to dispell them with a solution to implementing Enums in Eiffel. First and foremost, Why does Eiffel need an Enum type...

Why Does Eiffel Need an Enum Type

The reasons are numerous and leaves us asking why they were not approached in the ECMA specification.

As Eiffel evolves and rears its head into the mainstream, its application domain expands. Users, libraries and complexity all grows as a languages does. It has been long said that Eiffel is almost unique in its ability to self-document classes and routines through terse comments and contracts. However, Eiffel for being so terse with commenting, is extremely verbose with class interfaces which can be trying at times.

Enums for Brevity

To demonstrate a point of Eiffel's verboseness, due to the lack of an Enum type specification, turn you eyes to EV_TEXT_ALIGNABLE:

deferred class
    EV_TEXT_ALIGNABLE
 
feature -- Access
 
    text_alignment: INTEGER is
            -- Current alignment.
            -- See class EV_TEXT_ALIGNABLE_CONSTANTS for
            -- possible values.
        require
            not_destroyed: not is_destroyed
        ensure
            bridge_ok: Result = implementation.text_alignment
 
feature -- Status report
 
    is_left_aligned: BOOLEAN is
            -- Is `Current' left aligned?
        require
            not_destroyed: not is_destroyed
 
    is_center_aligned: BOOLEAN is
            -- Is `Current' center aligned?
        require
            not_destroyed: not is_destroyed
 
    is_right_aligned: BOOLEAN is
            -- Is `Current' right aligned?
        require
            not_destroyed: not is_destroyed
 
feature -- Status setting
 
    align_text_center is
            -- Display `text' centered.
        require
            not_destroyed: not is_destroyed
        ensure
            alignment_set: is_center_aligned
 
    align_text_right is
            -- Display `text' right aligned.
        require
            not_destroyed: not is_destroyed
        ensure
            alignment_set: is_right_aligned
 
    align_text_left is
            -- Display `text' left aligned.
        require
            not_destroyed: not is_destroyed
        ensure
            alignment_set: is_left_aligned
 
invariant
    valid_alignment:
        (create {EV_TEXT_ALIGNMENT_CONSTANTS}).valid_alignment (text_alignment)
 
end

In EV_TEXT_ALIGNMENT there is already is text_alignment, which exhibits the inherent problem with type saftey through the lack of an Enum type. No only is a flag attribute present but there are the status setting routines align_text_left, align_text_right and align_text_center. On top of that, for the sake of code clarity for clients there are the status queries is_left_aligned, is_right_aligned and is_center_aligned. The status setting and query routines hide the implementation details of having to know and use EV_TEXT_ALIGNMENT_CONSTANTS, which is a good thing but can also be very frustrating when writing effective code using this interface.

To demonstrate, assume a graphical editor has been developed using EiffelVision2. In the editor the user selects a region of text which should enabled tool bar button used to manipulate the alignment of the selected region to text.

on_text_selected
    require
        has_selection: has_selection
    local
        l_alignable: EV_TEXT_ALIGNABLE
    do
        l_alignable ?= selected_entity
        if l_alignable /= Void then
            if l_alignable.is_left_aligned then
                active_button := left_aligned_button
            elseif l_alignable.is_center_aligned then
                active_button := center_aligned_button
            elseif l_alignable.is_right_aligned then
                active_button := right_aligned_button
            else
                    -- New alignment not respect!
                check False end
            end
        end
        if active_button /= Void
            alignment_button_group.set_active_button (active_button)
            alignment_button_group.enable_sensitive
        else
            alignment_button_group.disable_sensitive
        end
    end

Using an Enum type for EV_TEXT_ALIGNMENT would change things dramatically. First off,

deferred class
    EV_TEXT_ALIGNABLE
 
feature -- Access
 
    text_alignment: EV_TEXT_ALIGNMENT assign set_text_alignment
            -- Current alignment.
        require
            not_destroyed: not is_destroyed
 
feature -- Status setting
 
    set_text_alignment (alignment: like text_alignment)
            -- Set `text_alignment' to `alignment'
        require
            not_destroyed: not is_destroyed
        ensure
            alignment_set: text_alignment = alignment
 
end

At first look out of seven features it has been reduced to only two. That alone is a major change in the amount of code that has to be written for a library author. The EV_TEXT_ALIGNABLE is actually deferred so that is also a lot less code an implementor has to write. It does not stop there, with the type safety introduced, by changing text_alignment to use a Enum type instead of an integer, the class invariant has been remove as have the reference comments needed in text_alignment to explain exactly which class needs to be used, containing the constants, to use text_alignment correctly.


 

on_text_selected

An Example

To be more concrete regarding the new type-safety, code brevity and safety in extending styles Enum types bring here is an example. The first example is written for Eiffel as it stands now. The example is from a fictious graphical tool used to modify the text alignment of any EV_TEXT_ALIGNABLE widget hosted as a child of a EiffelVision2 window. The important point about the example is that it handles creation, event hookup and event handling automatically.

feature {NONE} -- Initialization
 
    create_alignment_buttons
            -- Create alignment group tool bar buttons
        do
            add_alignment_button (once "left",
                {EV_TEXT_ALIGNMENT_CONSTANTS}.left)
            add_alignment_button (once "center", 
                {EV_TEXT_ALIGNMENT_CONSTANTS}.center)
            add_alignment_button (once "right",
                {EV_TEXT_ALIGNMENT_CONSTANTS}.right)
        end
 
    add_alignment_button (a_name: STRING; a_alignment: INTEGER) is
            -- Creates an adds an alignment tool bar button
        require
            a_name_attached: a_name /= Void
            not_a_name_is_empty: not a_name.is_empty
            valid_alignment:
                (create {EV_TEXT_ALIGNMENT_CONSTANTS}).valid_alignment (
                    a_alignment)
        local
            button: EV_BUTTON
        do
            create button.make
            button.set_pixmap (pixmap_loader (once "button_" + a_name))
            button.click_action.extend (agent
                on_alignment_button_clicked (a_alignment))
            alignment_group.extend (button)
        end
 
feature {NONE} -- Event handlers
 
    on_alignment_button_clicked (a_alignment: INTEGER) is
            -- Called when an alignment tool bar button is selected
        require
            valid_alignment:
                (create {EV_TEXT_ALIGNMENT_CONSTANTS}).valid_alignment (
                    a_alignment)
        local
            l_alignable: EV_TEXT_ALIGNABLE
        do
            l_alignable ?= selected_widget
            if l_alignable /= Void then
                inspect a_alignment
                when {EV_TEXT_ALIGNMENT_CONSTANTS}.left then
                    l_alignable.align_text_left
                when {EV_TEXT_ALIGNMENT_CONSTANTS}.center then
                    l_alignable.align_text_center
                when {EV_TEXT_ALIGNMENT_CONSTANTS}.right then
                    l_alignable.align_text_right
 
                    -- No else because if a new alignment is
                    -- added we want an exception to be raised.
 
                end
            end
        end

For a comparison in brevity here is the code using an Enum type:

feature {NONE} -- Initialization
 
    create_alignment_buttons
            -- Create alignment group tool bar buttons.
        do
            {EV_TEXT_ALIGNMENT}.items.do_all (agent (a_item: EV_TEXT_ALIGNMENT)
                local
                    button: EV_BUTTON
                do
                    create button.make
                    button.set_pixmap (pixmap_loader (
                        once "button_" + a_item.out))
                    button.click_action.extend (agent
                        on_alignment_button_clicked (a_item))
                    alignment_group.extend (button)
                end
        end
 
feature {NONE} -- Event handlers
 
    on_alignment_button_clicked (a_alignment: EV_TEXT_ALIGNMENT)
            -- Called when an alignment tool bar button is selected.
        local
            l_alignable: EV_TEXT_ALIGNABLE
        do
            l_alignable ?= selected_widget
            if l_alignable /= Void then
                l_alignable.text_alignment := a_alignment
            end
        end

A feature has been removed an replaced with an inline agents because it can be done using the items feature of an Enum type. Preconditions have also been removed to check the type of alignment because this will be statically checked by the compiler. Brevity and type safety are great additions but the most important here is the Enum type based code respected any new Enum type added at a later date, which the first non-Enum type base code cannot do!

The widget that is alignable is responsible for the adjustment in the display when an alignment is set on it. In the first example code the alignment is set using a routine that corresponds to a constant in another class. If a new constant is added, and a new alignment type established, the application will not show the tool bar button or be able to respect any new type in the event handler. The application author must be vigilantly aware of the changes to EV_TEXT_ALIGNMENT.

The next problem with the first code example is the precondition contract condition EV_TEXT_ALIGNMENT_CONSTANTS.valid_alignment will succeed even through add_alignment_button and on_alignment_button_clicked do not support it! This is because the modifier of EV_TEXT_ALIGNMENT_CONSTANTS will have surely added it to the list of supported alignments. The code is completely broken according to the contract conditions.

In the Enum-type example all these problems disappear. There is no need for the preconditions because of the type safety. The setting of the alignment is delegated directly to the widget itself, so any new style is automatically propagated.