3

immutable objects are OK, but, are OK non-final local references?

In other words, next code snippet can be represented as written in functional style?

Employee e = new Employee("Lex", 24, 250);
e = Employee.setName(e, "Vasili");
e = Employee.setAge(e, 12);
e = Employee.setSalary(e, 2500);
Employee.log(e);

P.S. here all Employee methods are static, setters are factory methods that returns new instances.

20
  • e0, e1, e2 are all undefined. Using setX and setY as method names here is misleading. Perhaps name them createWithX and createWithY. Commented Feb 22, 2018 at 18:28
  • 1
    what do you mean with OK? Commented Feb 22, 2018 at 18:29
  • 2
    An immutable style would probably be new Employee("Lex", 24, 250).withName("Vasili").withAge(12).withSalary(2500), with the with methods each returning a new Employee object. Commented Feb 22, 2018 at 18:48
  • 1
    @LexUshakov In pure functional languages, there are no "variables". There are names which refer to values, but you cannot change the value. In imperative programming terms, all variables are constants, i.e. not variable / varying / changeable at all. That is why, to simulate the same behavior in Java, make parameters and local variables final, so they cannot be re-assigned / updated / modified. Commented Feb 22, 2018 at 19:13
  • 2
    @LexUshakov, in functional programming there are no objects or references, there are only values, and values don't change. You don't change the value of 4 in a program, so if you have said that x=4 then you don't change x either, because that would be just the same as changing the value of 4. The first line says that e = Employee "Lex" etc. Then you change e to be Employee "Vasili". So is e Lex or Vasili? It can't be both. In FP there is no concept of "flow of control" in which variables have different values at different times. Commented Feb 23, 2018 at 8:48

4 Answers 4

2

Since this question is tagged 'java', I assume the question is on the FP practices (namely, immutability) in Java.

Today's good practice in Java is to use either a builder:

Employee e = Employee.builder()
                     .surname("Lex")
                     .age(24)
                     .name("Vasili")
                     .salary(2500)
                     .build();

or a static constructor:

Employee e = Employee.of("Vasili", "Lex", 24, 2500);

In both cases, the "classic" constructor should be declared private to ensure that the object can't be instantiated and made available to the client in inconsistent state.

Object mutators should then return the new object:

Employee.of("Vasili", "Lex", 24, 2500)  // creates an object
        .updateName("Sergey")           // returns 1st modified copy
        .updateSalary(3500);            // returns 2nd modified copy based on 1st copy

Following these practices, the need for non-final local references often vanishes.
A very popular example is the Date and Time API.

Now, on using mutable local variables. That's ok, but the code can be shortened and made more expressive using method chaining. Trying to chain the static methods as-is will not look very elegant:

Employee e = setSalary(setAge(setName(new Employee("Lex", 24, 250), "Vasili"), 12), 2500);

As an attempt to emulate a monad, one can wrap the object in some monad-like container which defines a bind method that accepts a function, which will accept the object stored in monad and return some result, which will again be wrapped in a monad. A simple example will look like this:

static class Employee {
    public String name;
    public int age;
    public long salary;
}

static class Monad<T> {
    private final T value;

    private Monad(T value) { this.value = value; }

    public static <T> Monad<T> of(T value) {
        return new Monad<>(value);
    }

    public T getValue() { return value; }

    public Monad<T> bind(UnaryOperator<T> operator){
        return of(operator.apply(value));
    }
}

public static void main(String[] args) {
    Employee value = Monad.of(new Employee())
                          .bind(e -> {e.name = "Lex"; return e; })
                          .bind(e -> {e.age = 24; return e; })
                          .bind(e -> {e.salary = 2500; return e; })
                          .getValue();
}

But this can be done with core Java since version 8 - Stream API can do this and much more:

Stream.of(new Employee())
      .map(e -> {e.name = "Lex"; return e; })
      .map(e -> {e.age = 24; return e; })
      .map(e -> {e.salary = 2500; return e; })
      .findFirst()
      .get();
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for reply, @jihor! In your examples mutable object is stored - but it can be replaced by immutable one with static methods references?
Can you help to understand, why next code doesn't work? pastebin.com/DMN18EQv
@LexUshakov map() method on stream of T expects Function<? super T, ?>. The working line will look as .map((e) -> Employee.setName(e, "Vasilii")).
1

Don't know anything about Haskell, but I believe you are trying to achieve something like this:

Employee e = new Employee("Lex")
    .setAge(25)
    .setSalary(2500)
    .setGender(Gender.Male);

This is just a result of chaining functions in the following way

public Employee setParam(param){
   this.param = param;
   return this;
}

but the methods are not static, they belong to the instance.

Also there would be no need to pass the instance as parameter.


Also:

  • this isn't a required keyword; in my example above, the two parameters have the same name, so without this the code would basically re-assign param's value to itself. If the parameters had a different name this is not required. Yet returning this is necessary as it represent a reference to the current instance.

E.g:

public Employee setParam(String param) {
    parameter = param; // parameter is a field in class Employee
    return this; // this "this", is still necessary
}
  • final variables

May limit you on what you are trying to achieve with your style

final Employee e = Employee.setName(e, "Name"); // invalid, e is unkown

// ----------------

final Employee e;
e = Employee.setName(e, "Name"); // invalid, e may not be initialized

// ----------------

final Employee e;
e = Employee.setName("Name"); // valid

// ----------------

final Employee e = null;
e = Employee.setName(e, "Name"); // invalid. e was already initalized to null

// ----------------

final Employee e = Employee.setName("Name"); // valid
e = Employee.setName("Name2"); // invalid, final variable already initialized

3 Comments

Thanks for reply, but I already know it. So, in other words, it's impossible to make fluent API without this keyword, right?
@LexUshakov this is not required, let me update my answer
When non-static methods are used, they, simply speaking, are binded to some struct via hidden this link. Imho this is essential, main feature of OOP langs.
1

It is similar to that in the terms that every function returns new item, in many FP languages (e.g. Haskell) you even are not able to update the value, just create new one:

let myBook = beginBook "Haskell"
let myBook' = addChapter (Chapter "Intro" ["Hello","World"]) myBook

So here beginBook function will return a book and then addChapter will return another book with some fields modified.

2 Comments

But are you allowed to re-let variable myBook in Haskell? That's what the question is asking, i.e. can a local variable be updated.
@Andreas, no, in haskell it's definitely impossible. Here myBook and myBook' are functions definitions, not data structure as java class is.
1

Will something like this work for you (ignore the bad use of Optional and ifPresent we can replace it with something more meaningful)?

    Optional.of( new Employee("Lex", 24, 250) )
    .map( e -> Employee.setName(e, "Vasili") )
    .map( e -> Employee.setAge(e, 12) )
    .map( e -> Employee.setSalary(e, 2500) )
    .ifPresent( e -> Employee.log(e) );

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.