📜 ⬆️ ⬇️

Kotlin overview and comparison with C #

From the author:


This article is a note on the knee and is rather a brief overview of Kotlin, including a small comparison with the C # language in terms of syntax. This is my opinion and my thoughts about this relatively young language in the world of the Java platform, which in my opinion has a good chance of success.


Kotlin is a statically typed object-oriented programming language compiled for Java platforms (also JavaScript). Developed since 2010 by JetBrains. The release took place not so long ago. The authors aimed to make the language more concise and type-safe than Java, and simpler than Scala. The consequence of simplification compared with Scala is also faster compilation and better language support in the IDE. In addition, when the company announced the development of this language, a storm of criticism fell upon it that developers would better bring the Scala plugin to mind (which, as I understand it, still does not have a normal IDE). However, for a company, a programming language is quite an important tool, and Java developers are not in a hurry to introduce new functionality into the language. And it’s not even the case that they don’t want it, but because there is too much code written and too many systems running on this platform. And here it is necessary to pull backward compatibility as ballast. And even if in the latest, 8 versions of the language, they added new features (like lambda expressions, for example), the Enterprise world did not rush to update the JVM, which makes programmers sit on the version that the customer has. As experience shows, some custom-made enterprises and companies have recently updated their machines to version 7, and it will be too expensive for a customer’s company to force several hundred machines to upgrade to version 8. From my point of view, such a latency of a language in development characterizes it as a sufficiently developed and powerful tool that can give an idea of ​​how often it is used. However, compared to other Java languages, it sometimes seems verbose, but this is my opinion as a person who has programmed enough in C # and used for example the same LINQ, lambda expressions, and other syntax sugar buns that make the code more compact.
Therefore, people at JetBrains decided to make a language that, with full compatibility with Java, will provide additional features that simplify the programmer’s daily work and increase productivity.

Acquaintance ...


I ran into him by chance. Programming in Java, I missed the buns from C # and I would like to somehow please myself and meet the requirements of the customer. After reviewing the Kotlin documentation, I realized that this is what I need. Documentation of 150 pages is quite easy to read, the language is simple to learn and quite concise. However, what I liked the most was that it has quite a lot in common with C # and working with the language becomes even more pleasant. Still, do not want to forget .NET.
')

Goodies ...


Work with classes


Well, now let's move on to the most interesting and consider some features of the language and what I like about it.
How to declare a class in Kotlin:
class Man { var name: String //var -   , val -   var age: Int constructor(name: String, age: Int) { this.name = name this.age = age } } 

Almost nothing unusual, except that the constructor is marked with the constructor keyword. In fact, this is a secondary constructor from the point of view of Kotlin (a), and the primary or primary constructor is part of the class header:
 class Man constructor(var name: String, var age: Int) //      class Man (var name: String, var age: Int) 

The exact same syntax is equivalent to the code that was described earlier. The variables name and age are also present in the class and were respectively created in the primary constructor with var (an interesting enough feature). At first glance it is unusual, but after a while you realize that it is very convenient. But the main constructor cannot contain any code, so there is an initialization block (init), which is called every time an object is created:
 class Man (var name: String, var age: Int){ init { //-  } } 

Interesting in my opinion. You can also make a chain of constructors:
 class Man (var name: String){ var name: String? = null //,  null,        ,    ,   C# var age: Int = 0 //   ,    , getter  setter    constructor(name: String, age: Int) : this(name) { this.age = age } } 

Interestingly implemented here are the properties and the complete syntax for the declaration:
 var <propertyName>: <PropertyType> [= <property_initializer>] [<getter>] [<setter>] 

The initializer, getter, and setter are optional when describing a class, as shown in the first example. If the variable is described as val, then setter is denied accordingly. How to describe properties:
 class Man { var name: String get() { return "Name man: $field" //field -   ,     .  getter    , field      } private set(value) { //       field = value } var age: Int constructor(name: String, age: Int) { this.name = name this.age = age } } 

Data classes


Of interest are Data Classes. These classes are used to store data and do nothing else. The compiler automatically deduces members from all properties declared in the main constructor:

This provides convenience when working with classes of this type:
 data class Man (var name: String, var age: Int) fun main(args: Array<String>) { var man = Man("Alex", 26) //     new println(man) // Man(name=Alex, age=26) //  val (name, age) = man //  : val name = man.component1(); val age = man.component2(); println(name) // Alex println(age) // 26 // copy() var man2 = man.copy() //  ,   var man2 = man.copy(age = 20) // ,     println(man2) //Man(name=Alex, age=20) } 

On this description of classes, I would like to finish and go to the part of the language that is its highlight.

Functions and Lambdas


Functions in Kotlin are declared using the fun keyword and can be defined globally without being tied to a specific class.
 fun f1(x: Int): Int { return x * 2 } //  fun f1(x: Int): Int = x * 2 //   fun main(args: Array<String>) { println(f1(5)) // 10 } 

Functions can also be called using infix notation when:

 //   Int infix fun Int.extent(x: Int): Int { return this + x } //  infix fun Int.extent(x: Int) = this + x fun main(args: Array<String>) { // -   infix  println(5 extent 10) // 15 //   println(5.extent(10)) } 

Functions also have named parameters and default argument values.
You can pass a variable number of arguments:
 fun <T> asList(vararg ts: T): List<T> { val result = ArrayList<T>() for (t in ts) // ts is an Array result.add(t) return result } fun main(args: Array<String>) { val list = asList(1, 2, 3) // ,     } 

Local functions are supported (C # 7.0 also implemented this function)
 fun f1(x: Man): String { fun isTeenager(age: Int): Boolean { return age in 13..19 } if (isTeenager(x.age)) return "Man teenager" return "Man is not a teenager" } 

Higher order functions and lambda expressions


Of particular interest is this part of the language. Higher-order functions are usually called functions that take other functions as arguments or return another function as a result. In this case, the basic idea is that the functions have the same status as other data objects. The use of higher-order functions leads to abstract and compact programs, taking into account the complexity of their calculations.
Consider an example of a higher order function:
 //   ,    ,     fun<T> List<T>.filter(transform: (T) -> Boolean): List<T> { val result = arrayListOf<T>() for (item in this) { if (transform(item)) { result.add(item) } } return result } fun main(args: Array<String>) { val list = arrayListOf(1, 4, 6, 7, 9, 2, 5, 8) val listEven = list.filter { item -> item % 2 == 0 } listEven.forEach { item -> print(item.toString() + " ") } // : 4 6 2 8 } 

This approach allows you to write LINQ-style code:
 strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() } 

The full syntax of the lambda expression is as follows:
 val sum = { x: Int, y: Int -> x + y } 

Moreover, if you leave additional annotations, it will look like this:
 val sum: (Int, Int) -> Int = { x, y -> x + y } 

Parameters are always indicated in parentheses, which are then passed to the body with ->.
One thing that is missing from the lambda expression syntax is the ability to specify the type of the return value. In most cases, this is unnecessary because the return type can be inferred automatically. However, if you need to specify it explicitly, you can use an alternative syntax: an anonymous function .
 fun(x: Int, y: Int): Int = x + y //  val listEven = list.filter(fun(item) = item % 2 == 0) 

Currying and partial application of the function


Consider as an example the currying and partial application of the function and compare the implementation on Kotlin and C #.
Some people sometimes confuse (and I did some time ago) the terms currying and partial use of a function and use them interchangeably. Both currying and partial application are ways to convert one kind of function into another.

Partial application of the function


Partial application takes a function with N parameters and a value for one of these parameters and returns a function with N-1 parameters, such that, when called, it will collect all the necessary values ​​(the first argument passed to the function of the partial application and the rest N-1 arguments passed to the return function). Thus, these two calls must be equivalent to a method with three parameters. In C #, delegates will be used for this. Of course, they are not a complete replacement for higher-order functions, but more than enough to demonstrate.
  class Program { static Int32 SampleFunc(Int32 a, Int32 b, Int32 c) { return a + b + c; } //  ApplyPartial             static Func<T2, T3, TResult> ApplyPartial<T1, T2, T3, TResult> (Func<T1, T2, T3, TResult> function, T1 arg1) { return (b, c) => function(arg1, b, c); } static Func<T3, TResult> ApplyPartial<T2, T3, TResult> (Func<T2, T3, TResult> function, T2 arg2) { return (c) => function(arg2, c); } static Func<TResult> ApplyPartial<T3, TResult> (Func<T3, TResult> function, T3 arg3) { return () => function(arg3); } static void Main(string[] args) { Func<Int32, Int32, Int32, Int32> function = SampleFunc; Func<Int32, Int32, Int32> partial1 = ApplyPartial(function, 1); Func<Int32, Int32> partial2 = ApplyPartial(partial1, 2); Func<Int32> partial3 = ApplyPartial(partial2, 3); var resp = partial3(); //      Console.WriteLine(resp); Console.ReadKey(); } } 

Generalizations make the ApplyPatrial method look more complicated than it actually is. The absence of higher order types in C # means that a method implementation is needed for each delegate we want to use. For this, you may need an Action family.
Sample Kotlin code:
 fun sampleFunc(a: Int, b: Int, c: Int): Int { return a + b + c } fun f3(a: Int, b: Int): Int { return sampleFunc(a, b, 3) } fun f2(a: Int): Int { return f1(a, 2) } fun f1(): Int { return f2(1) } //    - val sampleFunc = { a: Int, b: Int, c: Int -> a + b + c } val f3 = { a: Int, b: Int -> sampleFunc(a, b, 3) } val f2 = { a: Int -> f3(a, 2) } val f1 = { -> f2(1) } fun main(args: Array<String>) { println(f1()) // 6 } 

In Kotlin, as in C #, you need to create a separate function (object) to get a function with N-1 arguments. The approaches are the same for languages, only in Kotlin it is more convenient to do this due to a more compact syntax.

Carring


While a partial application converts a function with N parameters into a function with N-1 parameters, applying one argument, currying decomposes the function into functions from one argument. We do not pass any additional arguments to the Curry method, except for the function being transformed:

The implementation in C # will look like this:
  class Program { static Int32 SampleFunc(Int32 a, Int32 b, Int32 c) { return a + b + c; } static Func<T1, Func<T2, Func<T3, TResult>>> Curry<T1, T2, T3, TResult> (Func<T1, T2, T3, TResult> function) { return a => b => c => function(a, b, c); } static void Main(string[] args) { Func<Int32, Int32, Int32, Int32> function = SampleFunc; //    Func<Int32, Func<Int32, Func<Int32, Int32>>> f1 = Curry(function); Func<Int32, Func<Int32, Int32>> f2 = f1(1); Func<Int32, Int32> f3 = f2(2); Int32 result = f3(3); //     ... var curried = Curry(function); result = curried(1)(2)(3); Console.WriteLine(result); // 6 Console.ReadKey(); } } 

Code on Kotlin:
 fun curry(body: (a: Int, b: Int, c: Int) -> Int): (Int) -> (Int) -> (Int) -> Int { return fun(a: Int): (Int) -> (Int) -> Int { return fun(b: Int): (Int) -> Int { return fun(c: Int): Int = body(a, b, c) } } } //   fun curry(body: (a: Int, b: Int, c: Int) -> Int) = fun(a: Int) = fun(b: Int) = fun(c: Int) = body(a, b, c) fun main(args: Array<String>) { val f = curry { a: Int, b: Int, c: Int -> a + b + c } val response = f(1)(1)(1) println(response) } 

Inline function


The use of higher functions leads to overhead. Memory allocation, on objects of functions, and also the subsequent cleaning. In many cases, this kind of cost can be eliminated by substituting lambda expressions. Consider a function that accepts a function as parameters, accepts a lock object and functions, acquires a lock, performs functions, and removes the lock:
 fun <T> lock(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } } 

However, when you call, the object is created. Instead of creating an object, the compiler may insert the following code:
 l.lock() try { foo() } finally { l.unlock() } 

To force the compiler to do this, you need to add an inline modifier in the method declaration:
 inline fun lock<T>(lock: Lock, body: () -> T): T { // ... } 

However, you should not embed large functions, this can affect performance. If there is a need to embed not all functions, you can add the noinline modifier:
 inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { // ... } 


Conclusion...


Kotlin is quite an interesting language, which is a pleasure to learn. I like its compact syntax and the wide possibilities it provides. Separate merit is worth mentioning the fact that it can be used together with Java in one project, which is also quite interesting and gives greater flexibility when creating a project. This language allows you to quickly develop a program and, moreover, to make it quite beautiful. Similar syntax with the same C # makes it easier to master, and more pleasant. Therefore, if someone suddenly wants to switch to the Java platform from the .NET platform, this language will probably leave a pleasant impression.

PS an interesting opinion about this language as Java-programmers, and C #. Would you use Kotlin in your projects?

Source: https://habr.com/ru/post/281759/


All Articles