blogPrivate features

dlebansais's picture

In Eiffel (to the best of my knowledge), a class B that inherits from a parent class A has access to all features of A. So far this hasn't bothered me at all, to the point that I often change my private functions in C# to protected.

But recently, as I was writing Eiffel programs to practice SCOOP, it occurred to me that I must now write a lot more features than I would before. Why is that? The reason comes from SCOOP controlled objects: what used to be simple qualified feature calls must now be turned into unqualified calls to 'private' routines. Let's see that with a quick example:

    value_of_object_resource (some_object: SOME_CLASS): INTEGER
    local
        object_resource: RESOURCE
    do
        object_resource := some_object.get_resource
        result := object_resource.value
    end

With SCOOP, if some_object is separate, then object_resource has to be separate as well, which means it must be controlled before we can get its value:

    value_of_object_resource (some_object: separate SOME_CLASS): INTEGER
    local
        object_resource: separate RESOURCE
    do
        object_resource := some_object.get_resource
        result := value_of_resource(object_resource)
    end

    value_of_resource (a_resource: separate RESOURCE): INTEGER
    do
        result := a_resource.value
    end

So far, so good. However, the requirement that objects must be controlled has the unintended effect of sometimes turning classes into a list of small, atomic features. This change does more than doubling the size of the source code, it also changes the design of the class by making a lot of internal calls available to descendants.

Let's take a look at {ENCODING}.convert_to:

feature -- Conversion

    convert_to (a_to_encoding: ENCODING; a_string: READABLE_STRING_GENERAL)
            -- Convert `a_string' from current encoding to `a_to_encoding'.
            -- If either current or `a_to_encoding' is not `is_valid', or an error occurs during conversion,
            -- `last_conversion_successful' is unset.
            -- Conversion result can be retrieved via `last_converted_string' or `last_converted_stream'.
        require
            a_to_encoding_not_void: a_to_encoding /= Void
            a_string_not_void: a_string /= Void
        local
            l_is_unicode_conversion: BOOLEAN
            l_unicode_conversion: like unicode_conversion
        do
            l_unicode_conversion := unicode_conversion
            if
                l_unicode_conversion.is_code_page_convertable (code_page, a_to_encoding.code_page)
            then
                encoding_i := l_unicode_conversion
                l_is_unicode_conversion := True
            else
                encoding_i := regular_encoding_imp
            end

            encoding_i.reset
            if l_is_unicode_conversion then
                encoding_i.convert_to (code_page, a_string, a_to_encoding.code_page)
            elseif a_to_encoding.is_valid and then is_valid and then is_conversion_possible (a_to_encoding) then
                encoding_i.convert_to (code_page, a_string, a_to_encoding.code_page)
            end
        end

If a_to_encoding is separate, then a new feature must be added to make a qualified call to a_to_encoding.code_page

...
                l_unicode_conversion.is_code_page_convertable (code_page, to_encoding_code_page(a_to_encoding))
...

    to_encoding_code_page (a_to_encoding: separate ENCODING): like code_page
        do
            result := a_to_encoding.code_page
        end

My point is that, since the original version of ENCODING did not need this extra feature, there is probably no good reason to add it, from a design perspective. Therefore, SCOOP sort of forces you to design classes differently than what you would like to.

That would not be much of a problem (other than the obvious readability issue) if the new feature was private to the class, a possibility offered by several other languages but not in Eiffel. This sparked my curiosity, and I went through Object Oriented Software Construction, Eiffel, the language and Touch of Class. Alas, I couldn't find a good explanation of why to not allow features to be hidden to descendants. Can anyone come up with one?

Before SCOOP, if anyone wanted to expose a simple interface to clients but also to descendants, the option to write features made of big do...end blocks was there. This is no longer the case in the world of separate objects. It looks like this consequence was overlooked, and only the readability issue was acknowledged.

Comments

This was not overlooked but

manus_eiffel's picture

This was not overlooked but it was felt as a small consequence for having a safe concurrent model. Nevertheless we are currently discussing the possibility of removing that needs in some cases, and providing a way to do separate calls in line.

One example for the first case where it is not needed would be:


s: separate OBJECT

create s.make s.do_something


It is clear in the above case that the current processor is the only one with access to the `s' and thus there is no need to wrap the call for performing `s.do_something'.

For the second, we could have a way to make separate calls as in:


s: separate OBJECT ...

separate s.do_something


which has the effect that the call had if you put the wrapper. The concern some people are raising is that it lacks the wait condition. Indeed with a wrapper you could do require s.is_ready do s.do_something end, therefore you still need a wrapper if the wait condition is critical for the logic. In additon, one has to remember that consecutive calls such as :


separate s.do_something separate s.do_something


are not guaranteed to be contiguous, since another call to `s' can be inserted between the two. To have this guarantee you still have to go through the wrapper.

Currently I use

dlebansais's picture

Currently I use (credits to Jocelyn for reminding me about inline agents)

;(agent (s_local: separate OBJECT) do s_local.do_something end).call([s])

It's a bit verbose, but it does the job.

If people want some wait logic, then a wrapper is required too.

Syndicate content