Difference between revisions of "Enums in Eiffel"

m (Safety)
m (Syntax updated)
 
(22 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
[[Category:ECMA]]
 
[[Category:ECMA]]
{{UnderConstruction}}
+
{{Research}}
 +
Author: [[User:Paulb| Paul Bates]]
  
 
==Preface==
 
==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.
+
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 have been a number of comments from numerous developers regarding why Enums are ''"Bad''". Most languages exhibit bad ideals and some more than others. The cognition 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...
+
In this document I'll outline the potential dangers and the oddities found commonly with the "Enum" type and attempt to dispel 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==
 
==Why Does Eiffel Need an Enum Type==
Line 14: Line 15:
 
===Example Listing===
 
===Example Listing===
  
To demonstrate a number of point here, a real example is going to be used. The example below is code taken from [[EiffelVision2]]:
+
To demonstrate a number of points here, a real example is going to be used. The example below is code taken from [[:Category:EiffelVision2|EiffelVision2]]:
  
 
<code>[eiffel]
 
<code>[eiffel]
Line 22: Line 23:
 
feature -- Access
 
feature -- Access
  
     text_alignment: INTEGER is
+
     text_alignment: INTEGER
 
             -- Current alignment.
 
             -- Current alignment.
 
             -- See class EV_TEXT_ALIGNABLE_CONSTANTS for
 
             -- See class EV_TEXT_ALIGNABLE_CONSTANTS for
Line 33: Line 34:
 
feature -- Status report
 
feature -- Status report
  
     is_left_aligned: BOOLEAN is
+
     is_left_aligned: BOOLEAN
 
             -- Is `Current' left aligned?
 
             -- Is `Current' left aligned?
 
         require
 
         require
 
             not_destroyed: not is_destroyed
 
             not_destroyed: not is_destroyed
 
          
 
          
     is_center_aligned: BOOLEAN is
+
     is_center_aligned: BOOLEAN
 
             -- Is `Current' center aligned?
 
             -- Is `Current' center aligned?
 
         require
 
         require
 
             not_destroyed: not is_destroyed
 
             not_destroyed: not is_destroyed
  
     is_right_aligned: BOOLEAN is
+
     is_right_aligned: BOOLEAN
 
             -- Is `Current' right aligned?
 
             -- Is `Current' right aligned?
 
         require
 
         require
Line 50: Line 51:
 
feature -- Status setting
 
feature -- Status setting
  
     align_text_center is
+
     align_text_center
 
             -- Display `text' centered.
 
             -- Display `text' centered.
 
         require
 
         require
Line 57: Line 58:
 
             alignment_set: is_center_aligned
 
             alignment_set: is_center_aligned
  
     align_text_right is
+
     align_text_right
 
             -- Display `text' right aligned.
 
             -- Display `text' right aligned.
 
         require
 
         require
Line 64: Line 65:
 
             alignment_set: is_right_aligned
 
             alignment_set: is_right_aligned
 
          
 
          
     align_text_left is
+
     align_text_left
 
             -- Display `text' left aligned.
 
             -- Display `text' left aligned.
 
         require
 
         require
Line 79: Line 80:
  
 
===Enums for Brevity===
 
===Enums for Brevity===
Eiffel can be quite verbose when it comes to its class' interfaces. <code>[eiffel]EV_TEXT_ALIGNABLE</code> demonstates this very well. <code>[eiffel]EV_TEXT_ALIGNABLE</code> contains seven features; three used to query an "alignable" state, three to set it and the final <code>[eiffel]text_alignment</code> attribute, used to store a state code according to a constant definition defined elsewhere.
+
Eiffel can be quite verbose when it comes to its class' interfaces. <code>[eiffel]EV_TEXT_ALIGNABLE</code> demonstates this very well. <code>[eiffel]EV_TEXT_ALIGNABLE</code> contains seven features; three used to query an "alignable" state, three to set it and the final <code>[eiffel]text_alignment</code> attribute used to store a state code according to a constant definition defined elsewhere.
  
The alternative to the verbose ''Status report'' and ''Status setting'' features is to use a class attribute that represents an object's mode, state or another binary attribute. ''Flags'' can also be defined in this fashion where bit operations are used to extrapolate meaning. This does not lend itself well to a well designed interface. It is much easier for a developer to conceptualize design using words instead of a numerical constant and bit operations, hence such verboseness in the Eiffel exported interfaces today.
+
The alternative to the verbose ''status report'' and ''status setting'' features is to use a class' attribute that represents an object's mode or state. ''Flags'' can also be defined in this fashion where bit operations are used to extrapolate meaning. This does not lend itself to a well designed interface. It is much easier for a developer to conceptualize design using a lexicon instead of numerical constants and bit operations. As such, verboseness is inherent in the Eiffel exported interfaces today.
 +
 
 +
In <code>[eiffel]EV_TEXT_ALIGNMENT</code> there is already an attribute <code>[eiffel]text_alignment</code>, which exhibits the inherent problem with type safety through the lack of an Enum type (discussed futher down.) Not only is a flag attribute present but there are the status setting routines <code>[eiffel]align_text_left</code>, <code>[eiffel]align_text_right</code> and <code>[eiffel]align_text_center</code>. On top of that, for the sake of code clarity for clients there are the status queries <code>[eiffel]is_left_aligned</code>, <code>[eiffel]is_right_aligned</code> and <code>[eiffel]is_center_aligned</code>. The status setting and query routines hide the implementation details of having to know and use <code>[eiffel]EV_TEXT_ALIGNMENT_CONSTANTS</code>, 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 enable tool bar buttons used to manipulate the alignment of the selected region of text.
 +
 
 +
<code>[eiffel]
 +
on_text_selected
 +
    require
 +
        has_selection: has_selection
 +
    do
 +
        if attached selected_entity as l_alignable 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 attached active_button as button then
 +
            alignment_button_group.set_active_button (button)
 +
            alignment_button_group.enable_sensitive
 +
        else
 +
            alignment_button_group.disable_sensitive
 +
        end
 +
    end
 +
</code>
 +
 
 +
In the code snippet <code>[eiffel]if...elseif...end</code> has to be used in order to make the correct calls. Here an inspect statement would be much easier to read.
  
 
=== Self Documenting ===
 
=== Self Documenting ===
<code>[eiffel]EV_TEXT_ALIGNABLE.text_alignment</code> also shows yet another problem with using <code>[eiffel]INTEGER</code> or another such numerical type as a form of state representation - it's meaningless! Eiffel's ability to assist developers in self documentation is not apparent here. The only way to realized the underlying meaning of <code>[eiffel]text_alignment</code> is to read the comments, which indicates where to look for the associated constant values associated with an <code>[eiffel]EV_TEXT_ALIGNABLE</code> object's state.
+
<code>[eiffel]EV_TEXT_ALIGNABLE.text_alignment</code> also shows yet another problem with using <code>[eiffel]INTEGER</code> or another such numerical type as a form of state representation - it is meaningless! Eiffel's ability to assist developers in self documentation is not apparent here. The only way to realized the underlying meaning of <code>[eiffel]text_alignment</code> is to read the comments, which indicates where to look for the associated constant values associated with an <code>[eiffel]EV_TEXT_ALIGNABLE</code> object's state. This is of course assuming that the developer actually wrote a comment defining where to locate associated state constants.
  
For documentation, there is the class invariant check found in <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS.valid_alignment</code> but only those who wish to descend the implementation <code>[eiffel]EV_TEXT_ALIGNABLE</code> are bothered about the invariant as it only applies to the state validation of an <code>[eiffel]EV_TEXT_ALIGNABLE</code> object and not to a client. In addition <code>[eiffel]valid_alignment</code> could potentially return <code>[eiffel]True</code> for an non-respected value (more on this later.)
+
For documentation, there is the class invariant query function found in <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS.valid_alignment</code>. However only those who wish to descend the implementation <code>[eiffel]EV_TEXT_ALIGNABLE</code> are bothered about the invariant as it only applies to the state validation of an <code>[eiffel]EV_TEXT_ALIGNABLE</code> descended object and not to a client of it. In addition <code>[eiffel]valid_alignment</code> could potentially return <code>[eiffel]True</code> for an non-respected value, but more on this later.
 +
 
 +
For a simple 3 second readability test. Which of the following code snippets is easier to use?
 +
 
 +
<code>[eiffel]
 +
    text_alignment: INTEGER
 +
</code>
 +
 
 +
<code>[eiffel]
 +
    text_alignment: EV_TEXT_ALIGNMENT
 +
</code>
 +
 
 +
The question was rhetorically, the latter is always the clear winner even with comments. In fact the latter could be used with no comment at all and still be clear to a reader.
  
 
===Safety===
 
===Safety===
One of the major disadvantages of not having Enums is the lack of safety. Safety encompassed both type safety and future proofing.
+
One of the major disadvantages of not having Enum typess is the lack of safety. Safety herer encompassing both type safety and future proofing additional state members.
 +
 
 +
Type safety doesn't seem the obvious choice of nomenclature but it is apt. When speaking in basic numerical types and associated values type safety doesn't make any sense. When speaking in Enum types it does. <code>[eiffel]INTEGER</code> based status attributes, as seen in the <code>[eiffel]text_alignment</code> attribute above can hold '''any''' arbitrary value. It is only the protection of a class invariant that maintains that the value set to <code>[eiffel]text_alignment</code> is correct. Enum types have the ability to be statically checked at compile time so there is no ounce of ambiguity as to what state an Eiffel entity can hold, return or be passed.
 +
 
 +
A subsequent problem with the class invariant is that comes from the <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS</code> class. <code>[eiffel]valid_alignment</code> being located in another class does not guarantee <code>[eiffel]EV_TEXT_ALIGNABLE</code>'s integrity. If an <code>[eiffel]INTEGER</code> constant state is added to <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS</code>, it will probably be a valid state and so added to <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS.valid_alignment</code>. This is extremely bad. Every class using <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS</code> must now respect any additions of the state constants or remain broken according to their contracts. The contracts do not just have to be class invariants. Image a setter routine that is used to set <code>[eiffel]EV_TEXT_ALIGNABLE.text_alignment</code> directly. It also uses <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS.valid_alignement</code> as a precondition. A client of a <code>[eiffel]EV_TEXT_ALIGNABLE</code> derived class knowing of the new states, added to <code>[eiffel]EV_TEXT_ALIGNABLE_CONSTANTS</code>, passes one of the recent additional state constants to that setter. Instead of the precondition failing it passes through, even though <code>[eiffel]EV_TEXT_ALIGNABLE</code> has not been modified to respect the new state constants. Such scenarios are code maintenance nightmares and bring forth a plethora of potential bugs.
 +
 
 +
With Enum types type-safety is implicit because each state constant is of that Enum type, enforcing static compile-time checking. In addition, all contracts related to the validity of an <code>[eiffel]INTEGER</code> state constant can be removed because runtime checking is made redundant because of the static checking at compile time.
  
 
===A Quick Enum Conversion===
 
===A Quick Enum Conversion===
Using an Enum type for the exemplary <code>[eiffel]EV_TEXT_ALIGNMENT</code>, the code is reduced significatly:  
+
Using an Enum type for the exemplary <code>[eiffel]EV_TEXT_ALIGNMENT</code>, the code is reduced significantly:  
  
 
<code>[eiffel]
 
<code>[eiffel]
Line 117: Line 168:
 
</code>
 
</code>
  
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 <code>[eiffel]EV_TEXT_ALIGNABLE</code> 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 <code>[eiffel]text_alignment</code> to use a Enum type instead of an integer, the class invariant has been removed as have the reference comments needed in <code>[eiffel]text_alignment</code> to explain exactly which class needs to be used, containing the constants, to use <code>[eiffel]text_alignment</code> correctly.
+
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 <code>[eiffel]EV_TEXT_ALIGNABLE</code> 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 <code>[eiffel]text_alignment</code> to use a Enum type instead of an <code>[eiffel]INTEGER</code>, the class invariant has been removed as have the reference comments needed in <code>[eiffel]text_alignment</code> to explain exactly which class needs to be used, containing the constants, to use <code>[eiffel]text_alignment</code> correctly.
  
 
== An Example ==
 
== An Example ==
  
To be more concrete regarding the type-safety, code brevity and safety in extending styles new Enum types could bring, here is an example. The first example is written for Eiffel as it stands now. The example is from a fictitious graphical tool used to modify the text alignment of any <code>[eiffel]EV_TEXT_ALIGNABLE</code> descended 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.
+
To be more concrete regarding the type-safety, code brevity and future proofing new Enum types could bring, here is an example. The first example is written for Eiffel as it stands now. The example is from a fictitious graphical tool used to modify the text alignment of any <code>[eiffel]EV_TEXT_ALIGNABLE</code> descended 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.
  
 
<code>[eiffel]
 
<code>[eiffel]
Line 137: Line 188:
 
         end
 
         end
  
     add_alignment_button (a_name: STRING; a_alignment: INTEGER) is
+
     add_alignment_button (a_name: STRING; a_alignment: INTEGER)
 
             -- Creates an adds an alignment tool bar button
 
             -- Creates an adds an alignment tool bar button
 
         require
 
         require
Line 157: Line 208:
 
feature {NONE} -- Event handlers
 
feature {NONE} -- Event handlers
  
     on_alignment_button_clicked (a_alignment: INTEGER) is
+
     on_alignment_button_clicked (a_alignment: INTEGER)
 
             -- Called when an alignment tool bar button is selected
 
             -- Called when an alignment tool bar button is selected
 
         require
 
         require
Line 163: Line 214:
 
                 (create {EV_TEXT_ALIGNMENT_CONSTANTS}).valid_alignment (
 
                 (create {EV_TEXT_ALIGNMENT_CONSTANTS}).valid_alignment (
 
                     a_alignment)
 
                     a_alignment)
        local
 
            l_alignable: EV_TEXT_ALIGNABLE
 
 
         do
 
         do
 
             l_alignable ?= selected_widget
 
             l_alignable ?= selected_widget
             if l_alignable /= Void then
+
             if attached selected_widget as l_alignable then
 
                 inspect a_alignment
 
                 inspect a_alignment
 
                 when {EV_TEXT_ALIGNMENT_CONSTANTS}.left then
 
                 when {EV_TEXT_ALIGNMENT_CONSTANTS}.left then
Line 202: Line 251:
 
                         on_alignment_button_clicked (a_item))
 
                         on_alignment_button_clicked (a_item))
 
                     alignment_group.extend (button)
 
                     alignment_group.extend (button)
                 end
+
                 end)
 
         end
 
         end
 
      
 
      
Line 209: Line 258:
 
     on_alignment_button_clicked (a_alignment: EV_TEXT_ALIGNMENT)
 
     on_alignment_button_clicked (a_alignment: EV_TEXT_ALIGNMENT)
 
             -- Called when an alignment tool bar button is selected.
 
             -- Called when an alignment tool bar button is selected.
        local
 
            l_alignable: EV_TEXT_ALIGNABLE
 
 
         do
 
         do
             l_alignable ?= selected_widget
+
             if attached selected_widget as l_alignable then
            if l_alignable /= Void then
+
 
                 l_alignable.text_alignment := a_alignment
 
                 l_alignable.text_alignment := a_alignment
 
             end
 
             end
Line 226: Line 272:
  
 
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.
 
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.
 +
 +
== Defining Eiffel Enum Type ==
 +
I'm not going to propose any syntax for Enum types because it is open for debate about how Enum types should appear in Eiffel, what semantics Enum type will have and to what level of declaration should they be defined for. Enums can be as simple a declaring manifest numerical constants to fully fledged objects.
  
 
== A Temporary Solution ==
 
== A Temporary Solution ==
  
Enum types can be created in Eiffel as it stands today, albeit not perfectly. I've managed to create psuedo Enum types using Eiffel 6.0, but each Enum type requires a little more from an Enum type author than should be needed. In addition the psuedo Enum types are based on basic numeric classes so Enum type members '''have''' to be declared as constants of a basic numeric type. This is no ideal but it works.
+
Enum types can be created in Eiffel as it stands today, albeit not perfectly. I've managed to create psuedo Enum types using Eiffel 6.0, but each Enum type requires a little more from an Enum type author than should be needed. In addition the psuedo Enum types are based on basic numeric classes so Enum type members '''have''' to be declared as constants of a basic numeric type. This is not ideal but it works.
  
 
=== A Pseudo Enum Type ===
 
=== A Pseudo Enum Type ===
Line 235: Line 284:
  
 
<code>[eiffel]
 
<code>[eiffel]
indexing
+
note
 
     description: "[
 
     description: "[
 
         Base Enum implementation.
 
         Base Enum implementation.
Line 264: Line 313:
 
feature {ENUM} -- Initialization
 
feature {ENUM} -- Initialization
  
     frozen default_create is
+
     frozen default_create
 
             -- Default initialization
 
             -- Default initialization
 
         do
 
         do
Line 270: Line 319:
 
         end
 
         end
  
     frozen make (n: G) is
+
     frozen make (n: G)
 
             -- Initializes instance from base entity `n'
 
             -- Initializes instance from base entity `n'
 
             --
 
             --
Line 296: Line 345:
 
             create l_internal
 
             create l_internal
 
             l_id := l_internal.dynamic_type (Current)
 
             l_id := l_internal.dynamic_type (Current)
             Result ?= internal_items_table.item (l_id)
+
             if attached internal_items_table.item (l_id) as temp then
             if Result = Void then
+
            Result := temp
                 check not_internal_items_table_has_l_id: not internal_items_table.has (l_id) end
+
             else
 +
            Result := Void
 +
                 check not_internal_items_table_has_l_id: not internal_items_table.has
 +
                    (l_id) end
 
                 l_items := members
 
                 l_items := members
 
                 l_count := l_items.count
 
                 l_count := l_items.count
Line 318: Line 370:
 
             result_attached: Result /= Void
 
             result_attached: Result /= Void
 
             not_result_is_empty: not Result.is_empty
 
             not_result_is_empty: not Result.is_empty
             internal_items_table_has_current: internal_items_table.has ((create {INTERNAL}).dynamic_type (Current) )
+
             internal_items_table_has_current: internal_items_table.has (
 +
                (create {INTERNAL}).dynamic_type (Current))
 
         end
 
         end
  
     frozen hash_code: INTEGER is
+
     frozen hash_code: INTEGER
 
             -- Hash code value
 
             -- Hash code value
        local
 
            l_hashable: HASHABLE
 
 
         do
 
         do
             l_hashable ?= internal_value
+
             if attached internal_value as l_hashable then
            if l_hashable /= Void then
+
 
                 Result := l_hashable.hash_code
 
                 Result := l_hashable.hash_code
 
             else
 
             else
Line 336: Line 386:
 
feature {ENUM} -- Access
 
feature {ENUM} -- Access
  
     frozen internal_items_table: HASH_TABLE [ARRAY [ENUM [NUMERIC]], INTEGER] is
+
     frozen internal_items_table: HASH_TABLE [ARRAY [ENUM [NUMERIC]], INTEGER]
 
             -- Items table used to cache member info.
 
             -- Items table used to cache member info.
 
         once
 
         once
Line 349: Line 399:
 
feature -- Query
 
feature -- Query
  
     is_valid_numeric_value (n: G): BOOLEAN is
+
     is_valid_numeric_value (n: G): BOOLEAN
 
             -- Determines if `n' is a value associated with a member of `Current'.
 
             -- Determines if `n' is a value associated with a member of `Current'.
 
             --
 
             --
Line 367: Line 417:
 
feature {NONE} -- Factory
 
feature {NONE} -- Factory
  
     members: ARRAY [G] is
+
     members: ARRAY [G]
 
             -- Array of all members of `Current'.
 
             -- Array of all members of `Current'.
 
             --
 
             --
Line 395: Line 445:
 
feature -- Comparison
 
feature -- Comparison
  
     frozen is_equal (other: like Current): BOOLEAN is
+
     frozen is_equal (other: like Current): BOOLEAN
 
             -- Is `other' attached to an object considered
 
             -- Is `other' attached to an object considered
 
             -- equal to current object?
 
             -- equal to current object?
Line 401: Line 451:
 
             -- `other': Other instance to compare against.
 
             -- `other': Other instance to compare against.
 
             -- `Result': True if Current is equal to `other', False otherwise.
 
             -- `Result': True if Current is equal to `other', False otherwise.
        local
 
            l_other: ENUM [G]
 
 
         do
 
         do
             l_other ?= other
+
             if attached other as l_other then
            if l_other /= Void then
+
 
                 Result := internal_value.is_equal (l_other.internal_value)
 
                 Result := internal_value.is_equal (l_other.internal_value)
 
             end
 
             end
Line 416: Line 463:
 
             -- `Result': True if Current is less than `other', False otherwise.
 
             -- `Result': True if Current is less than `other', False otherwise.
 
         local
 
         local
            l_other: ENUM [G]
 
 
             l_cc, l_oc: PART_COMPARABLE
 
             l_cc, l_oc: PART_COMPARABLE
 
         do
 
         do
             l_other ?= other
+
             if attached other as l_other then
            if l_other /= Void then
+
 
                 l_cc ?= internal_value
 
                 l_cc ?= internal_value
 
                 l_oc ?= l_other.internal_value
 
                 l_oc ?= l_other.internal_value
Line 431: Line 476:
 
feature -- Conversion
 
feature -- Conversion
  
     frozen item: G is
+
     frozen item: G
 
             -- `Current' as a {NUMERIC} value
 
             -- `Current' as a {NUMERIC} value
 
         do
 
         do
Line 442: Line 487:
 
feature -- Output
 
feature -- Output
  
     out: STRING is
+
     out: STRING
 
             -- New string containing terse printable representation
 
             -- New string containing terse printable representation
 
             -- of current object
 
             -- of current object
Line 482: Line 527:
 
feature {NONE} -- Factory
 
feature {NONE} -- Factory
  
     members: ARRAY [NATURAL] is
+
     members: ARRAY [NATURAL]
 
             -- Array of all members of `Current'
 
             -- Array of all members of `Current'
 
         once
 
         once
Line 515: Line 560:
 
Note, <code>[eiffel]ENUM</code>'s <code>[eiffel]item</code> member has to be used with <code>[eiffel]inspect</code> because the compiler does not recognized the psuedo Enum types are constants or the fact that they can be implicitly converted to a constant value.
 
Note, <code>[eiffel]ENUM</code>'s <code>[eiffel]item</code> member has to be used with <code>[eiffel]inspect</code> because the compiler does not recognized the psuedo Enum types are constants or the fact that they can be implicitly converted to a constant value.
  
<code>[eiffel]ENUM</code> also has a feature call <code>[eiffel]items</code> containing all available members of the Enum. With this code can be created an shown in the previous section. Pseudo Enum types, however, there is no good string representation of each member. Using <code>[eiffel]ENUM.out</code> will yield the numerical value of the Enum.
+
<code>[eiffel]ENUM</code> also has a feature call <code>[eiffel]items</code> containing all available members of the Enum. With this code can be created as shown in the previous section. Pseudo Enum types, however, there is no good string representation of each member. Using <code>[eiffel]ENUM.out</code> will yield the numerical value of the Enum.
  
 
It's a start.
 
It's a start.

Latest revision as of 17:13, 8 May 2013

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

Author: Paul Bates

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 have been a number of comments from numerous developers regarding why Enums are "Bad". Most languages exhibit bad ideals and some more than others. The cognition 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 potential dangers and the oddities found commonly with the "Enum" type and attempt to dispel 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 grow 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.

Example Listing

To demonstrate a number of points here, a real example is going to be used. The example below is code taken from EiffelVision2:

deferred class
    EV_TEXT_ALIGNABLE
 
feature -- Access
 
    text_alignment: INTEGER
            -- 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 `Current' left aligned?
        require
            not_destroyed: not is_destroyed
 
    is_center_aligned: BOOLEAN
            -- Is `Current' center aligned?
        require
            not_destroyed: not is_destroyed
 
    is_right_aligned: BOOLEAN
            -- Is `Current' right aligned?
        require
            not_destroyed: not is_destroyed
 
feature -- Status setting
 
    align_text_center
            -- Display `text' centered.
        require
            not_destroyed: not is_destroyed
        ensure
            alignment_set: is_center_aligned
 
    align_text_right
            -- Display `text' right aligned.
        require
            not_destroyed: not is_destroyed
        ensure
            alignment_set: is_right_aligned
 
    align_text_left
            -- 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

Enums for Brevity

Eiffel can be quite verbose when it comes to its class' interfaces. EV_TEXT_ALIGNABLE demonstates this very well. EV_TEXT_ALIGNABLE contains seven features; three used to query an "alignable" state, three to set it and the final text_alignment attribute used to store a state code according to a constant definition defined elsewhere.

The alternative to the verbose status report and status setting features is to use a class' attribute that represents an object's mode or state. Flags can also be defined in this fashion where bit operations are used to extrapolate meaning. This does not lend itself to a well designed interface. It is much easier for a developer to conceptualize design using a lexicon instead of numerical constants and bit operations. As such, verboseness is inherent in the Eiffel exported interfaces today.

In EV_TEXT_ALIGNMENT there is already an attribute text_alignment, which exhibits the inherent problem with type safety through the lack of an Enum type (discussed futher down.) Not 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 enable tool bar buttons used to manipulate the alignment of the selected region of text.

on_text_selected
    require
        has_selection: has_selection
    do
        if attached selected_entity as l_alignable 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 attached active_button as button then
            alignment_button_group.set_active_button (button)
            alignment_button_group.enable_sensitive
        else
            alignment_button_group.disable_sensitive
        end
    end

In the code snippet if...elseif...end has to be used in order to make the correct calls. Here an inspect statement would be much easier to read.

Self Documenting

EV_TEXT_ALIGNABLE.text_alignment also shows yet another problem with using INTEGER or another such numerical type as a form of state representation - it is meaningless! Eiffel's ability to assist developers in self documentation is not apparent here. The only way to realized the underlying meaning of text_alignment is to read the comments, which indicates where to look for the associated constant values associated with an EV_TEXT_ALIGNABLE object's state. This is of course assuming that the developer actually wrote a comment defining where to locate associated state constants.

For documentation, there is the class invariant query function found in EV_TEXT_ALIGNABLE_CONSTANTS.valid_alignment. However only those who wish to descend the implementation EV_TEXT_ALIGNABLE are bothered about the invariant as it only applies to the state validation of an EV_TEXT_ALIGNABLE descended object and not to a client of it. In addition valid_alignment could potentially return True for an non-respected value, but more on this later.

For a simple 3 second readability test. Which of the following code snippets is easier to use?

text_alignment: INTEGER
text_alignment: EV_TEXT_ALIGNMENT

The question was rhetorically, the latter is always the clear winner even with comments. In fact the latter could be used with no comment at all and still be clear to a reader.

Safety

One of the major disadvantages of not having Enum typess is the lack of safety. Safety herer encompassing both type safety and future proofing additional state members.

Type safety doesn't seem the obvious choice of nomenclature but it is apt. When speaking in basic numerical types and associated values type safety doesn't make any sense. When speaking in Enum types it does. INTEGER based status attributes, as seen in the text_alignment attribute above can hold any arbitrary value. It is only the protection of a class invariant that maintains that the value set to text_alignment is correct. Enum types have the ability to be statically checked at compile time so there is no ounce of ambiguity as to what state an Eiffel entity can hold, return or be passed.

A subsequent problem with the class invariant is that comes from the EV_TEXT_ALIGNABLE_CONSTANTS class. valid_alignment being located in another class does not guarantee EV_TEXT_ALIGNABLE's integrity. If an INTEGER constant state is added to EV_TEXT_ALIGNABLE_CONSTANTS, it will probably be a valid state and so added to EV_TEXT_ALIGNABLE_CONSTANTS.valid_alignment. This is extremely bad. Every class using EV_TEXT_ALIGNABLE_CONSTANTS must now respect any additions of the state constants or remain broken according to their contracts. The contracts do not just have to be class invariants. Image a setter routine that is used to set EV_TEXT_ALIGNABLE.text_alignment directly. It also uses EV_TEXT_ALIGNABLE_CONSTANTS.valid_alignement as a precondition. A client of a EV_TEXT_ALIGNABLE derived class knowing of the new states, added to EV_TEXT_ALIGNABLE_CONSTANTS, passes one of the recent additional state constants to that setter. Instead of the precondition failing it passes through, even though EV_TEXT_ALIGNABLE has not been modified to respect the new state constants. Such scenarios are code maintenance nightmares and bring forth a plethora of potential bugs.

With Enum types type-safety is implicit because each state constant is of that Enum type, enforcing static compile-time checking. In addition, all contracts related to the validity of an INTEGER state constant can be removed because runtime checking is made redundant because of the static checking at compile time.

A Quick Enum Conversion

Using an Enum type for the exemplary EV_TEXT_ALIGNMENT, the code is reduced significantly:

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 removed 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.

An Example

To be more concrete regarding the type-safety, code brevity and future proofing new Enum types could bring, here is an example. The first example is written for Eiffel as it stands now. The example is from a fictitious graphical tool used to modify the text alignment of any EV_TEXT_ALIGNABLE descended 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)
            -- 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)
            -- Called when an alignment tool bar button is selected
        require
            valid_alignment:
                (create {EV_TEXT_ALIGNMENT_CONSTANTS}).valid_alignment (
                    a_alignment)
        do
            l_alignable ?= selected_widget
            if attached selected_widget as l_alignable 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
            (create {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.
        do
            if attached selected_widget as l_alignable then
                l_alignable.text_alignment := a_alignment
            end
        end

A feature has been removed and replaced with an inline agent 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 respects 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 general purpose event handler routine. The application author must be vigilantly aware of the changes made to EV_TEXT_ALIGNMENT.

The next problem with the first code example is the precondition contract condition EV_TEXT_ALIGNMENT_CONSTANTS.valid_alignment. The contract will succeed in passing through even through add_alignment_button and on_alignment_button_clicked do not support a new type of alignment! 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.

Defining Eiffel Enum Type

I'm not going to propose any syntax for Enum types because it is open for debate about how Enum types should appear in Eiffel, what semantics Enum type will have and to what level of declaration should they be defined for. Enums can be as simple a declaring manifest numerical constants to fully fledged objects.

A Temporary Solution

Enum types can be created in Eiffel as it stands today, albeit not perfectly. I've managed to create psuedo Enum types using Eiffel 6.0, but each Enum type requires a little more from an Enum type author than should be needed. In addition the psuedo Enum types are based on basic numeric classes so Enum type members have to be declared as constants of a basic numeric type. This is not ideal but it works.

A Pseudo Enum Type

Here is the code for a psuedo Enum type:

note
    description: "[
        Base Enum implementation.
    ]"
 
deferred class
    ENUM [G -> NUMERIC]
 
inherit
    HASHABLE
        redefine
            default,
            default_create,
            is_equal,
            out
        end
 
    PART_COMPARABLE
        redefine
            default_create,
            is_equal,
            out
        end
 
convert
    item: {G}
 
feature {ENUM} -- Initialization
 
    frozen default_create
            -- Default initialization
        do
            make ((items[1]).item)
        end
 
    frozen make (n: G)
            -- Initializes instance from base entity `n'
            --
            -- `n': A numerical value associated with a member of `Current'
        require
            n_is_valid_numeric_value: is_valid_numeric_value (n)
        do
            internal_value := n
        ensure
            internal_value_set: internal_value = n
        end
 
feature -- Access
 
    frozen items: ARRAY [like Current]
            -- Access to all members of `Current'
        local
            l_items: ARRAY [G]
            l_instance: like Current
            l_internal: INTERNAL
            l_id: INTEGER
            l_count, i: INTEGER
            l_assert: BOOLEAN
        do
            create l_internal
            l_id := l_internal.dynamic_type (Current)
            if attached internal_items_table.item (l_id) as temp then
            	Result := temp
            else
            	Result := Void
                check not_internal_items_table_has_l_id: not internal_items_table.has
                    (l_id) end
                l_items := members
                l_count := l_items.count
                create Result.make (1, l_count)
 
                l_assert := {ISE_RUNTIME}.check_assert (False)
                from i := 1 until i > l_count loop
                        -- Does automatic conversion
                    l_instance ?= l_internal.new_instance_of (l_id)
                    l_instance.make (l_items.item (i))
                    Result.put (l_instance, i)
                    i := i + 1
                end
                l_assert := {ISE_RUNTIME}.check_assert (l_assert)
 
                internal_items_table.force (Result, l_id)
            end
        ensure
            result_attached: Result /= Void
            not_result_is_empty: not Result.is_empty
            internal_items_table_has_current: internal_items_table.has (
                (create {INTERNAL}).dynamic_type (Current))
        end
 
    frozen hash_code: INTEGER
            -- Hash code value
        do
            if attached internal_value as l_hashable then
                Result := l_hashable.hash_code
            else
                check False end
            end
        end
 
feature {ENUM} -- Access
 
    frozen internal_items_table: HASH_TABLE [ARRAY [ENUM [NUMERIC]], INTEGER]
            -- Items table used to cache member info.
        once
            create Result.make (1)
        ensure
            result_attached: Result /= Void
        end
 
    frozen internal_value: G
            -- Internal raw value (do not rename!)
 
feature -- Query
 
    is_valid_numeric_value (n: G): BOOLEAN
            -- Determines if `n' is a value associated with a member of `Current'.
            --
            -- `n': A numerical value to check for validity against members of `Current'.
            -- `Result': True if `n' is a valid member, False otherwise.
        local
            l_assert: BOOLEAN
        do
            Result := True
            l_assert := {ISE_RUNTIME}.check_assert (False)
                -- Kind of a hack but it's the most direct way to check.
                -- `n' is converted into `like Current'
            Result := members.has (n)
            l_assert := {ISE_RUNTIME}.check_assert (l_assert)
        end
 
feature {NONE} -- Factory
 
    members: ARRAY [G]
            -- Array of all members of `Current'.
            --
            -- Note to Implementers: This function should be a once!
        deferred
        ensure
            result_attached: Result /= Void
            not_result_is_empty: not Result.is_empty
            result_lower_is_one: Result.lower = 1
            result_upper_is_count: Result.upper = Result.count
            result_contains_unique_items: (agent (a_result: ARRAY [G]): BOOLEAN
                require
                    a_result_attached: a_result /= Void
                local
                    l_upper, i: INTEGER
                do
                    Result := True
                    l_upper := a_result.upper
                    from i := a_result.lower until i > l_upper or not Result loop
                        Result := a_result.occurrences (a_result[i]) = 1
                        i := i + 1
                    end
                end).item ([Result])
            same_result: Result = members
        end
 
feature -- Comparison
 
    frozen is_equal (other: like Current): BOOLEAN
            -- Is `other' attached to an object considered
            -- equal to current object?
            --
            -- `other': Other instance to compare against.
            -- `Result': True if Current is equal to `other', False otherwise.
        do
            if attached other as l_other then
                Result := internal_value.is_equal (l_other.internal_value)
            end
        end
 
    frozen infix "<" (other: like Current): BOOLEAN
            -- Is current object less than `other'?
            --
            -- `other': Other instance to compare against.
            -- `Result': True if Current is less than `other', False otherwise.
        local
            l_cc, l_oc: PART_COMPARABLE
        do
            if attached other as l_other then
                l_cc ?= internal_value
                l_oc ?= l_other.internal_value
                if l_cc /= Void and l_oc /= Void then
                    Result := l_cc < l_oc
                end
            end
        end
 
feature -- Conversion
 
    frozen item: G
            -- `Current' as a {NUMERIC} value
        do
            Result := internal_value
        ensure
            result_set: Result = internal_value
            result_is_valid_numeric_value: is_valid_numeric_value (Result)
        end
 
feature -- Output
 
    out: STRING
            -- New string containing terse printable representation
            -- of current object
        do
            Result := internal_value.out
        end
 
invariant
    is_valid_numeric_value: is_valid_numeric_value (internal_value)
 
end

A Custom Pseudo Enum Type

The following code shows the declaration of a new psuedo Enum type as possible in Eiffel 6.0:

expanded class
    BORDER_STYLE
 
inherit
    ENUM [NATURAL]
 
create
    default_create,
    make
 
convert
    make ({NATURAL}),
    item: {NATURAL}
 
feature -- Access
 
    none: NATURAL = 1
    flat: NATURAL = 2
    rounded: NATURAL = 3
    embossed: NATURAL = 4
 
feature {NONE} -- Factory
 
    members: ARRAY [NATURAL]
            -- Array of all members of `Current'
        once
            Result := <<none, flat, rounded, embossed>>
        end
 
end

With psuedo Enum types create declarations, convert declarations and members needs to be implemented and populated correctly.

Using Pseudo Enum Types

And now the usage. Because each psuedo Enum type member is declared as a basic numeric type constant they can be accessed as such.

usage (style: BORDER_STYLE)
    do
        inspect style.item
        when {BORDER_STYLE}.none then
            ...
        when {BORDER_STYLE}.flat then
            ...
        when {BORDER_STYLE}.rounded then
            ...
        when {BORDER_STYLE}.embossed then
            ...
        else
        end
    end

Note, ENUM's item member has to be used with inspect because the compiler does not recognized the psuedo Enum types are constants or the fact that they can be implicitly converted to a constant value.

ENUM also has a feature call items containing all available members of the Enum. With this code can be created as shown in the previous section. Pseudo Enum types, however, there is no good string representation of each member. Using ENUM.out will yield the numerical value of the Enum.

It's a start.