5

I'm trying to make an immutable POINT class in Eiffel. Is the code below defines one? The {NONE} accessibility for the x and y fields is enough for it? Can I write something to the class invariant like x = x', or how else can I achieve immutability?

class POINT
    create
        make
    feature {NONE}
        x: DOUBLE
        y: DOUBLE
    feature
        make (x_: DOUBLE; y_: DOUBLE)
        do
            x := x_
            y := y_
        ensure
            set: x = x_ and y = y_
        end

    feature --accessors
        get_x: DOUBLE
        do
            Result := x
        ensure
            Result = x
        end
    end
Ferenc Dajka
  • 981
  • 2
  • 12
  • 39
  • 2
    note according to the eiffel style guide accessors are nouns, therefore no get prefix. It will make your code easier to read (this is true of almost all languages). But also when you make the attribute its own accessors see answer below, it won't have a get prefix. – ctrl-alt-delor Feb 25 '14 at 14:38

1 Answers1

5

Eiffel does not allow to change attributes of a class by its clients. For example, the following code is rejected:

p: POINT
...
p.x := 5.0

As a result, there is no need to provide getters (like in some other languages). You can just use p.x provided that x is sufficiently exported to a client class. So, the code of your example can be simplified to

class POINT
create
   make
feature -- Access
   x: DOUBLE
   y: DOUBLE
feature {NONE} -- Creation
   make (x0: DOUBLE; y0: DOUBLE)
      -- Initialize object with `x0' and `y0'.
   do
      x := x0
      y := y0
   ensure
      set: x = x0 and y = y0
   end
end

Note that the creation procedure is not exported anymore, otherwise it would be possible to use it as a normal (i.e. non-creation) routine and to change the attributes, that is we would be able to do something as follows

create p.make (1, 1)
p.make (2, 3)
print (p)

and this would print (2, 3), i.e. you would be able to change the value of the original object p, making it mutable.

Though, now the attributes cannot be changed directly, it's still possible to call feature copy on an object of type POINT and to change the whole object. If you want to avoid this situation as well, the feature can be redefined in the class POINT to raise an exception, or even to have a postcondition False causing the run-time to raise an exception for you:

copy (other: like Current)
      -- <Precursor>
   do
      (create {EXCEPTIONS}).raise ("Attempt to change an immutable object.")
   ensure then
      is_allowed: False
   end
nbro
  • 12,226
  • 19
  • 85
  • 163
Alexander Kogtenkov
  • 5,605
  • 1
  • 24
  • 33
  • 1
    Note that if you do want to use "p.x := 5.0", you can use an assigner. See: http://docs.eiffel.com/book/examples/example-self-initializing-attributes-and-assigner-commands . – Louis M Jan 06 '14 at 01:09
  • I don't get it, what happens if I set the make to private? now if I call: create p1.make(1, 1); print(p1); create p1.make(2, 3);print(p1) than I get a result: (1, 1) (2, 3) and the same happens when the visibility is not set – Ferenc Dajka Jan 07 '14 at 13:25
  • and if I inherit from this class, I can still redefine the make feature, but {NONE} states that even the child classes can't change the feature. or the create method has some different rules about it? – Ferenc Dajka Jan 07 '14 at 13:39
  • 1
    @FerencDajka, there are two different exports for a feature - feature export status (that is `NONE` for `make` - as specified after keyword `feature`) and creation export status (that is `ANY` for `make` - there is no restriction after keyword `create` in the declaration). In other words, with the code as written you can use `make` only as a creation procedure, not as a regular feature. With a regular feature you could do `create p.make (1, 1); p.make (2, 3); print (p)` that would give `(2, 3)` - i.e. you would be able to change the value of the original object, making it mutable. – Alexander Kogtenkov Jan 07 '14 at 16:54
  • @FerencDajka, as to you second question - export status has nothing to do with redeclaration restrictions. It's still OK to redefine `make` in a descendant or even to change its export status. If you want to stick to a particular implementation of a feature you would precede its name with a keyword `frozen`. However this is not considered a good practice as preconditions and postconditions can be used to specify desired behavior without requiring a particular implementation that could be improved (e.g. for efficiency, or generalized) in a descendant. – Alexander Kogtenkov Jan 07 '14 at 17:00
  • okay, many thanks for your answers @AlexanderKogtenkov , but to be honest it was a bit hard to figure out, what's the point of the {NONE} part, so for the others coming to this page you should modify your answer to something like "after adding {NONE} you can only call create p1.make(1, 1), what initializing a new object on the memory, but not p1.make(1, 1) (without the create keyword) what would only set the attributes of the existing p1 object" – Ferenc Dajka Jan 08 '14 at 09:15