diff --git a/README.md b/README.md index 80247b42e8..a300ca299f 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,195 @@ A part of this work is to explore to what extent contract logic can expressed mo type and DSL system that Kotlin provides, in order to answer the question: how powerful does a DSL system need to be to achieve good results? -This code may evolve into a runnable prototype that tests the clarity of thought behind the R3 architectural proposals. \ No newline at end of file +This code may evolve into a runnable prototype that tests the clarity of thought behind the R3 architectural proposals. + +---- + +# Kotlin in two minutes + +Here's a brief description of syntax and features you will see in this code. Kotlin almost always maps directly to Java +so it should not be hard to understand. In some cases the Java equivalents are shown. + + fun foo() = 1234 + fun foo(): Int = 1234 + +Defines a function called foo that returns the single expression 1234. The return type is inferred in the first example. + + val x = 1234 in java: final int x = 1234; + var y = "a string" in java: String y = "a string"; + +Defines an immutable and mutable variable with inferred types. + + data class Foo(val x: Int, val y: String) : Bar() + + ... in Java: + + public class Foo extends Bar { + private int x; + private String y; + + public Foo(int x, String y) { + this.x = s; this.y = y; + } + + public int getX() { return x; } + public String getY() { return y; } + + @Override public boolean equals(Object other) { .... } + @Override public int hashCode() { .... } + @Override public String toString() { .... } + } + +Defines a final JavaBean that inherits from Bar, with auto-generated getX(), getY() methods, a constructor that takes +both as arguments, equals, hashCode and toString implementations, as well as a useful method called copy() which is a +way to duplicate an object with one or more fields changed. Kotlin methods can have named arguments with default values, +so copy is auto-generated as: + + fun copy(x: Int = x, y: String = y) = Foo(x, y) + +This avoids the tedious Java builder pattern. + +If the "data" modifier is missing then the equals/toString/hashCode/copy methods are not auto-generated. The reason +is that currently the compiler doesn't always know how to handle inheritance scenarios. However java-style get/set/is +methods are always generated. + + class Foo(val v: Int) + fun List.sum() = filterNotNull().map { it.v }.sum() + + ... java equivalent: + + public class Foo { + private int v; + public Foo(int v) { this.v = v; } + public int getV() { return v; } + } + + public class FileNameKt { + public static int sum(List list) { + int c = 0; + for (Foo foo : list) { + if (foo != null) c += foo.getV(); + } + return c; + } + } + +Defines a simple class with an int field. Then defines an _extension function_ on List, which is like a static +utility method but it appears in auto-complete at the right times, and can be used to integrate externally defined +classes e.g. from libraries with language features better. The type Foo? means "a possibly null reference to a Foo". If +the question mark is missing then references are guaranteed to be non-null. Kotlin generates nullability assertions +in the right places and tracks the nullness of types through the code flow. Finally, this example uses functional +programming utilities to express the Java loop in a simpler way. The Kotlin compiler will inline filterNotNull(), map() +and sum() so the actual bytecode generated is pretty similar to the Java, however, in this case an additional collection +is allocated to hold the results of filtering out the null objects. + +TIPS: + +1. Whenever you see a function call and want to know what it does, you can command/ctrl click on it to treat it like a + hyperlink and go to the definition. +2. Put the cursor on a function call and press Ctrl-J to pop up the javadoc for it. That's also a fast way to learn + how the code works. + + + object Foo : Bar() {} + + ... in java: + + public class Foo { + public Foo INSTANCE = new Foo(); + private Foo() {} + } + +Defines a singleton called Foo. + + inline fun List.doSomething { + for (i in this) { + if (i is T) { + i.someMethodOfFoo() + } + } + } + +This is a more complex example you'll only find in the DSL definition. It can't be easily expressed in Java. + +Java does not have reified generics. That means when a generic method or class is compiled, every use of a type +variable is replaced with its bound (or Object, if there is no bound). So you can't access the type inside +the code itself, which is awkward. Kotlin allows you to duck around this limitation in limited circumstances. Here, +we define an extension function that applies to every list that can't contain nulls, which takes a single type +variable Y which must either be Foo, or some subclass of Foo. Then for every item that is of that type, it calls +a method on it. + +Note that the act of testing the type automatically narrows the type of the variable! Put more simply: + + val x: Any = .... + if (x is String) + println("it is ${x.length} characters long") + + ... in java: + + Object x = ....; + if (x instanceof String) { + String xAsString = (String) x; + System.out.println("it is " + xAsString.length() + " characters long"); + } + +In Kotlin we don't need the type cast immediately after the check. + + sealed class Foo { + class Bar + class Baz + } + +Defines a class called Foo that has two final inner subclasses. Foo _cannot_ have any other subclasses, so you know +that it's safe to do this: + + val f: Foo = ... + val result = when (f) { + is Bar -> 1 + is Baz -> 2 + } + +A "when" expression is like a switch in Java, but it can return something. The compiler will flag an error if we didn't +handle all the cases, so if someone adds another case to Foo we'll be sure to update all the places where the type is +switched on. + + infix fun Bar.`something long and wordy`(i: Int): Foo = .... + val f = bar `something long and wordy` 42 + + .... in java: + + public class BarUtils { + public static Foo somethingLongAndWordy(Bar b, int i) { ... } + } + Foo f = BarUtils.somethingLongAndWordy(bar, 42); + +Defines an extension function that has spaces in its name, marked such that it can be used "infix". You cannot define +functions with such names in Java, though the JVM allows it. This is purely a syntax tweak but it can make DSLs more +easily read. + +Collections: Kotlin uses the ordinary Java collections framework but enhances it with some compiler magic and lots of +extension functions. Differences are: + +* List is a read-only view of a list of foos that cannot contain nulls. +* MutableList is the same thing as a java.util.List, in that it lets you add/remove elements. +* Map is a read-only map of (non-null) Foos to Bars +* List.contains and Map.get in Java takes a parameter of type Object, meaning you can accidentally try and look up + an entry of the wrong type and the compiler won't stop you. In Kotlin the types are narrowed to catch this error. +* You can write "someMap[someKey]" in Kotlin to do get/put on a map. + + + val m = hashMapOf( + "a" to 1, + "b" to 2, + "c" to 3 + ) + println(m["a"]) + + in java: + + Map m = new HashMap<>(); + m.put("a", 1); + m.put("b", 2); + m.put("c", 3); + System.out.println(m.get("a")); +