Kotlin Basics
Inheritance
After a class is created, it can be instantiated. However, sometimes it is necessary to add additional functionality to a class. This can be done through class inheritance, and is one of the key requirements of command-based programming. The official Kotlin documentation may be more comprehensive, but this is a general overview of how it works:
open class Parent{ // the keyword 'open' specifies that this class can be extended
var foo = 0
open fun doSomething(){ // the keyword 'open' can be used before a function to allow it to be overridden by child classes
// do stuff
}
}
class Child: Parent(){ // the colon specifies that Child inherits from a new instance of class 'Parent', you may also use 'object' in place of 'class' in this case
override fun doSomething(){ // this is what will now run if an instance of 'Child' has 'doSomething()' called
super.foo++ // the keyword 'super' is used to access the parent class' data
super.doSomething() // this allows you to run the function that exists in the parent class, so this will "do stuff"
}
}
Constructors
Kotlin uses init blocks as part of constructors. These allow you to run functions or initialize variables upon class or object instantiation.
class ExampleClass {
var foo = 0
init { // this runs after foo is initialized, but before bar is initialized
foo++
}
var bar = 0
init{ // you may have multiple init blocks throughout a class
foo++
bar++
}
// when ExampleClass is instantiated, foo will be 2, and bar will be 1
}
Singletons
While "objects" typically refer to anything created with the class keyword, Kotlin has its own special object keyword.
An object, while functionally similar to a class, is limited to one singular instance.
This is known as a "singleton."
Singletons are especially useful in robotics, as they provide clean organization for subsystems without having to pass a singular instance around everywhere.
Instead, you simply import the object and reference it as if it were a typical instance of a class.
TODO: Give example of how they work and are different from classes.
Scope Functions
Read the Kotlin docs for comprehensive information.
The one you will probably use most often is .apply{}.
.apply{} runs methods on an object, but it removes the need to repeatedly reference the object. Instead of someObject.methodOne() and then someObject.methodTwo(), you can do someObject.apply{methodOne(); methodTwo()}, removing redundant references to the object.
For example, you can use this when creating motors:
val motor = TalonFX(1).apply{
outputInverted = true
otherThing = false
someNumber = 2.0
}
// The above lines are functionally the same as:
val motor = TalonFX(1)
motor.outputInverted = true
motor.otherThing = false
motor.someNumber = 2.0
Lambdas
Oftentimes, you will want to pass a function as a parameter to another function. This is typically part of WPILib's command-based framework, but you may also use it in other places. Lambdas are essentially anonymous functions that can be passed around and run wherever needed.
InstantCommand() is a good example of this:
{Subsystem.doSomething()} is passed to InstantCommand(), which will run it once when the command is scheduled.
Lambdas can also take parameters, for example:
Sometimes, WPILib may require a "Consumer," which is a lambda that takes parameters but does not return anything. For example:
Here, the lambda takes anInt parameter called number, and prints it when run.
Note
You can remove parenthesis in Kotlin if the lambda is the last parameter in a function call, so the above can also be written as:
Null Protection
Kotlin has built-in null protection, which prevents null pointer exceptions.
This is done through the use of ? and !!.
A variable that may be null is declared with a ? after its type, for example:
var maybeNull: String? = null // this variable may be null
var notNull: String = "Hello" // this variable may not be null
? or !! to indicate how you want to handle the potential null value.
Using ? will safely access the variable, returning null if it is null:
Using !! will throw a null pointer exception if the variable is null:
There is also an operator called the Elvis operator ?: (because it looks like sideways elvis), which allows you to provide a default value if a variable is null:
Small Notes
- Semicolons aren't needed in Kotlin, but they can be used just like in Java and other C-based languages if needed. Here are some use cases:
- Inline statements, e.g.
{doOneThing(); doAnotherThing()} - Adding functionality to enum classes, e.g.
enum class State(val position: Double){ UP(1.0), DOWN(0.0) ; // The semicolon separates enum values from additional class members // This specifies that each value of State is declared, opening the rest of the body to functions fun isPastHalfwayUp(): Boolean{ // Simple example function return position > 0.5 } } // Now you can use this function elsewhere State.UP.isPastHalfwayUp() // returns true - Kotlin can read and use Java files. You may import them and use them like a normal Kotlin file if needed.