The SPOT Architecture Applied to Class ARRAYED_LIST

by Finnian Reilly (modified: 2024 Mar 16)

Introduction

(From Wikipedia) In computer science, the single point of truth (SPOT) architecture, is the practice of structuring information models such that every data element is mastered (or edited) in only one place.

The Vision-2 libraries do a nice job of applying this principle to the job of assigning default values to attributes in a descendant of EV_ANY. Any call to default_create results in a call to initialize. By redefining initialize in descendants you can guard against a common problem. If instead of having a SPOT for initializing attribute, they are duplicated over a number of different make-routines, it can lead to a situation where the developer forgets to assign a default value for one particular attribute with undesirable results. To a certain extent, void-safety protects the developer from the worst possible outcome of a void-target call, but what if the correct default value is a once-constant or some other non-standard value ?

A Descendant of ARRAYED_LIST

In Eiffel-Loop there is a class EL_ARRAYED_LIST extending ARRAYED_LIST that has very many initialization routines compared to the parent. This proved problematic for a class EL_TUPLE_TYPE_LIST inheriting EL_ARRAYED_LIST as the routine initialize needs to be called no matter which of the 7 possible make-routines are called. After adding some extra make-routines I found that non_conforming_list had not been initialized.

class EL_TUPLE_TYPE_LIST [T] inherit EL_ARRAYED_LIST [TYPE [T]] rename make as make_list, make_from_tuple as make_list_from_tuple redefine initialize end create make, make_from_static, make_from_tuple, make_from_array, make_from_if, make_from_list, make_from_special feature {NONE} -- Initialization make (type_array: EL_TUPLE_TYPE_ARRAY) do make_list (type_array.count) across type_array as type loop -- skip non-conforming types if attached {like item} type.item as l_type then extend (l_type) elseif not all_conform then non_conforming_list.extend (type.item) else create non_conforming_list.make (1) non_conforming_list.extend (type.item) end end end make_from_static (static_type: INTEGER) do make (create {EL_TUPLE_TYPE_ARRAY}.make_from_static (static_type)) end initialize do Precursor compare_objects non_conforming_list := Empty_list end make_from_tuple (tuple: TUPLE) do make (create {EL_TUPLE_TYPE_ARRAY}.make_from_tuple (tuple)) end feature -- Access non_conforming_list: like Empty_list -- items in `type_array' argument to `make' routine that do not conform to type `T'

SPOT Applied to EL_ARRAYED_LIST

Rather then solve the problem at the level of EL_TUPLE_TYPE_LIST I decided for posterity to solve the issue at the level of EL_ARRAYED_LIST by redefining some of the routines found in the ELKS base class ARRAYED_LIST.

As you can see, every possible call to a make routine results in a call to initialize which just initializes index to zero. Redefining this routine in descendants is sufficient to ensure that all attributes have their proper default value no matter which make-routine is called.

class EL_ARRAYED_LIST [G] inherit ARRAYED_LIST [G] rename make_filled as make_default_filled, append as append_sequence undefine index_of redefine make, make_from_array, make_default_filled end EL_CHAIN [G] rename accommodate as grow undefine off, occurrences, has, do_all, do_if, there_exists, for_all, is_equal, search, copy, i_th, at, last, first, valid_index, is_inserted, move, start, finish, go_i_th, put_i_th, force, append_sequence, prune, prune_all, remove, swap, new_cursor, to_array, order_by redefine find_next_item, joined, push_cursor, pop_cursor end EL_MODULE_EIFFEL create make, make_empty, make_default_filled, make_filled, make_from_for, make_from, make_from_if, make_joined, make_from_special, make_from_array, make_from_sub_list, make_from_tuple convert make_from_array ({ARRAY [G]}), to_array: {ARRAY [G]} feature {NONE} -- Initialization make (n: INTEGER) -- Allocate list with `n' items. -- (`n' may be zero for empty list.) do make_from_special (create {like area}.make_empty (n)) end make_default_filled (n: INTEGER) -- Allocate list with `n' items. -- (`n' may be zero for empty list.) -- This list will be full. do make_from_special (create {like area}.make_filled (({G}).default, n)) end make_from_array (a: ARRAY [G]) -- Create list from array `a'. do make_from_special (a.area) end make_empty do make (0) end make_filled (n: INTEGER; new_item: FUNCTION [INTEGER, G]) do make (n) from until full loop extend (new_item (count + 1)) end end make_from (container: CONTAINER [G]) -- initialize from `container' items do make_from_for (container, create {EL_ANY_QUERY_CONDITION [G]}) end make_from_for (container: CONTAINER [G]; condition: EL_QUERY_CONDITION [G]) -- initialize from `container' of items for all items meeting `condition' local wrapper: EL_CONTAINER_WRAPPER [G] do create wrapper.make (container) if attached wrapper.query (condition) as result_list then make_from_special (result_list.area) object_comparison := container.object_comparison else make_empty end end make_from_if (container: CONTAINER [G]; condition: PREDICATE [G]) -- initialize from `container' of items if `condition (item)' is true do make_from_for (container, create {EL_PREDICATE_QUERY_CONDITION [G]}.make (condition)) end make_from_list (list: ITERABLE [G]) do if attached {ARRAYED_LIST [G]} list as arrayed_list then make_from_special (arrayed_list.to_array.area) else make (Iterable.count (list)) across list as l_path loop extend (l_path.item) end end end make_from_special (a_area: like area) do area_v2 := a_area initialize end make_from_sub_list (list: READABLE_INDEXABLE [G]; start_index, end_index: INTEGER) require valid_range: start_index <= end_index implies list.lower <= start_index and list.upper <= end_index local i: INTEGER do if end_index < start_index then make_empty else make (end_index - start_index + 1) from i := start_index until i > end_index loop extend (list [i]) i := i + 1 end end end make_from_tuple (tuple: TUPLE) -- extend Current with items in `tuple' of type conforming to `G' local i: INTEGER do make (tuple.count) from i := 1 until i > tuple.count loop if attached {G} tuple.item (i) as l_item then extend (l_item) end i := i + 1 end end make_joined (array_1, array_2: ARRAY [G]) do make (array_1.count + array_2.count) append (array_1); append (array_2) end initialize -- initialize default attribute values do index := 0 end