Scala's Implicit that even Java shops can understand

After seeing Implicit's announcement at ScalaMatsuri 2018, I finally understood it, so I will write about Implicit from the perspective of using Java. In addition, since Scala is like an amateur, there is a great possibility that articles will be modified or deleted.

Problem that the number of methods increases too much when Interface is implemented

In java, declare how to handle the object with Interface and implement the method of it. For example, if instances x1 and x2 of a class X are comparable, implement the Comparable interface in X. And since this Comparable object can be sorted, it can be passed to Collection.sort () etc.


public class X implements Comparable<X> {
    public int i;

    public X(int i) {
        this.i = i;
    }

    @Override
    public int compareTo( X x) {
        return this.i - x.i;
    }

    @Test
    public void testSort(){
        List<X> list = new ArrayList<X>();
        list.add(new X(3));
        list.add(new X(1));
        list.add(new X(0));
        list.add(new X(2));
        Collections.sort(list);
    }
}

Since Interface is a declaration of how to handle it, it can be implemented as much as it can be treated like A or B. However, if you implement it carelessly, you will also implement a large number of Interface methods.


public interface A {
    void a1();
    void a2();
}

public class X implements Comparable<X>, A, B, C {
    public int i;

    public X(int i) {
        this.i = i;
    }

    @Override
    public int compareTo( X x) {
        return this.i - x.i;
    }

    @Override
    public void a1() {
    }

    @Override
    public void a2() {
    }

    @Override
    public void b1() {
    }

    @Override
    public void b2() {
    }

    @Override
    public void c1() {
    }

    @Override
    public void c2() {
    }
}

This is not good. Simply having too many methods can hurt your outlook, and reviewers may get angry if the convention limits the number of methods per class. And in fact, these methods aren't very relevant, so there's not much benefit to writing them in the same place. Implementing Comparable for sorting in the first place means that we wouldn't implement it without the need for sorting, which means it has nothing to do with the essence of what the object is. The class is "what it is", the Interface (type) is "what can be treated as it", and the declaration is easier to see by writing it in the same place, but the implementation does not have much merit to mix.

Split the class

So let's consider splitting the class. Methods for satisfying interfaces such as Comparable can often be done by creating a class that is a wrapper for the original object. Let's assume that X that Implements this Comparable is a ComparableX class, so that toComparableX () can be performed from X.


public class ComparableX implements Comparable<ComparableX> {
	public X x;

	public ComparableX(X x) {
		this.x = x;
	}

	@Override
	public int compareTo(ComparableX comparableX) {
		return this.x.i - comparableX.x.i;
	}
}

public class X {
	public int i;

	public X(int i) {
		this.i = i;
	}

	public ComparableX toComparableX() {
		return new ComparableX(this);
	}

	public A toA() {
		return new XinA(this);//Suppose you make a wrapper called class XinA implements A.
	}

	public B toB() {
		return new XinB(this);
	}

	public C toC() {
		return new XinC(this);
	}
}

If you want to sort, you can make a list of this Comparable X, sort it, and extract the X inside. Similarly, you can define classes with other interfaces. Now the outlook is better. Actually, you should write the constructor and factory on the wrapper side rather than writing toA () or toB ().


class A {
    public A(X x){}
    public static A getInstance(X x){}
}

A conversion code is one that increases the number of conversion destinations. However, it is not the problem of X that the conversion destination increases, but the problem of other class code that requests the conversion destination. It is unreasonable to change X every time the number of conversion destinations increases with A and B.

If you divide it, you will not be able to handle it as it is

However, there is a problem that X cannot be handled as it is by sandwiching methods, constructors, and factories.


A xA= x.toA()
aFnc(xA)
aFnc(new A(x))
aFnc(A.getInstance(x))
//aFnc(X)I wish I could write it directly ...

Also, if you have multiple methods that return objects of the same type, you may not be able to determine which one to use.


class X{
//Which one should I use! ??
    public A toWonderfulA(){}
    public A toGratestA(){}
}

On the contrary, there is a possibility that the names will collide, and to be honest, I would like to write them separately for each interface. So, as a delusion that cannot be realized with Java, let's think about a language that can write the same class separately for each interface that implements it.


public class X {
	public int i;

	public X(int i) {
		this.i = i;
	}
}

public class X implements Comparable<X> {
	public int compareTo(X x) {
		return this.i - x.i;
	}
}

public class X implements A {
	public void a1() {
	}

	public void a2() {
	}

}

I think the outlook is very good. Of course, this code cannot be compiled. I'd like the compiler to take advantage of the fact that they are in the same class and merge them with care.

You can do it with Scala

But Scala can write similar code. Yes, Implicit.


case class X(i: Int) {
}

//Something like Interface
trait A[T] {
    def a1():Int
    def a2():Int
}

object X {

//You can write the code "when another type is requested for X" in Implicit. This simply returns a value that can be retrieved from X.
  implicit def xToInt(x:X): Int = x.i

// class X { toOrderingX(){return new Ordering()}}Same as.Comparator rather than comparable because X is not directly bound. This Ordering in sorted.compare()Sort by passing two elements X of the list to and comparing
  implicit val toOrderingX: Ordering[X] = new Ordering[X] {
    override def compare(x: X, y: X): Int = x.i - y.i
  }

//To normal code it looks like a class X implimentes Ordering, or an X with an Ordering comparator
  val xList = List(new X(3), new X(1), new X(2), new X(0))
  val sortedList = xList.sorted

// class X { toA(){return new A(this)}}Same as. You can bind the argument x directly, but I haven't seen it as sample code because it doesn't look like a type class.
  implicit def xToA(x:X): A[X] = new A[X] {
    def a1()=x.i
    def a2(){}
  }

//Looks like class X implimentes A ... but just new A(new X(1))Is it easier to think that it works as?
  aFnc(new X(1))

}


Implicit is called implicit type conversion, but from the Java shop's point of view, it's like a retrofit interface. Classes and interfaces can be treated as types in the same way, but they are a little different in nature. In this way, it is possible to separate what the class is and how to handle it from the outside, which is useful for writing a simpler model.

Implicit exists between a class and a type

A method of a class is a block of code that operates on an instance of that class. Then, isn't it easier to manage the code that targets the same instance if it is written together in that class? That's one of the basic object-oriented ideas.


//Functions can be placed anywhere so they are free but difficult to manage
def f(x : X) : Y = ...
def g(x : X) : Y = ...

//A method is a block of code associated with a class instance. Moves as X is passed as this
class X {
    def f() = ...
    def g() = ...
}

Implicit is a block of code tied to a "relationship where a class requires another type". It seems that the code I wrote as a delusion in Java earlier actually works.


//I want you to connect to "when X is requested Comparable" (compile does not pass
public class X implements Comparable<X> {
	public int compareTo(X x) {
		return this.i - x.i;
	}
}

//I want you to connect to "when X is requested to type A" (compile does not pass)
public class X implements A {
	public void a1() {
	}

	public void a2() {
	}

}


//Implicit leads to "when X is requested type A" (compiles through)
  implicit def xToA(x:X): A[X] = new A[X] {
    def a1()=...
    def a2(){}
  }

Relationship with DDD

The domain model should be independent of anything other than the domain. The basic implementation of this is to make it independent of other layers, but on the contrary, I think it is also necessary to avoid code that depends on other layers as much as possible. For example, if you have a domain model that doesn't assume sorting, should you implement Comparable just because it's convenient to be able to sort when displayed on the screen? If you add code to the model just because it is convenient, the model loses its purity. You may be able to write more domain-specific code by using Implicit.

Also, we want to unify the domain model as much as possible, but in different contexts it may be designed as another model. For example, suppose you have an object that is related to sales. This object is a product when it is sold, but it is just a cargo when it is transported to a store, and the buyer will use it as he or she wants, regardless of the convenience of the seller. In other words, the object is different depending on the context. In books etc., it is written to write a converter to convert the domain model, but it is not uncommon for a converter to have an unstable standing position and multiple similar ones are created, and "converter is a model Barren controversies such as "Is it part of the domain?" Implicit can be used to make the compiler aware of the converter and have the potential to manage it. (Currently, it doesn't seem to be the recommended usage.)

Implicit problems

It's hard to understand where it's written or where it should be written

A method is a function that is closely tied to a class and is at least physically co-located with X. The interface is also declared next to the class in most languages, so the declaration is in the same place. Implicit destroys it. It's just that it is not always written in the same place as the class because it is declared outside the class. Should the code when X is requested for type A be in the same place as X? Should it be in the same place as A? Where should X and A be written when they cannot be written in the same place in the existing class? This is a difficult and serious problem. Because the class is a description of "what it is", but there are traditions and guidelines for writing "what it is", but there is no guideline for writing "what it is when it is recognized as that". is.

The relationship must be self-explanatory, but it cannot be determined whether it is self-evident

For example, if an Int is required to behave as a Float, it would be obvious that the Int could be a Float. On the other hand, it's not obvious if Float can be an Int. Many languages round down, but there are other rounding methods such as rounding. It's not uncommon to make unexpected conversions because it's not uncommon to think about whether it's convenient or not when converting one piece of data to another type of data, but not so often.

Bad name for implicit type conversion

If you translate Implicit literally, it will be implicit, but What I wanted to do was tell the compiler about the type conversion, that is, it was a declaration, so I should have named it declarative type conversion or something like that. Type conversion is also a bit questionable. Is "something that fills the gap between the class and the required type" a type conversion? However, I'm not familiar with Scala or English, so this is just an excuse.

Impressions

Implicit has the impression that the concept is correct and gives the code freedom, but it is easy to abuse and lacks guidelines for writing correctly.

Recommended Posts

Scala's Implicit that even Java shops can understand
Basics of jQuery that even freeters can understand
"Inheritance" that even beginners of object-oriented programming can understand
Polymorphism that even beginners of object-oriented programming can understand
Android environment construction that even monkeys can understand [React Native]
Explaining "object-oriented" that even elementary school students can understand in 5 levels
Introduction to Java that can be understood even with Krillin (Part 1)
JSP + Eclipse + Jetty development environment construction that even Java beginners can do
Understand java constructor
[JavaScript] Java programmers! You can understand JavaScript closures very easily!
Write a class that can be ordered in Java
Play with Java function nodes that can use Java with Node-RED
[JQuery] A guy that even beginners could understand well