The following class shows example implementations of methods add, printNum, triple, produceName and longestOf:
class FunctionsClassic {
fun add(num1: Int, num2: Int): Int = num1 + num2
fun printNum(num: Int) {
print(num)
}
fun triple(num: Int): Int = num * 3
fun produceName(name: String): Name = Name(name)
fun longestOf(
str1: String,
str2: String,
str3: String,
): String = maxOf(str1, str2, str3, compareBy { it.length })
}
data class Name(val name: String)
Your task is to write similar classes, but instead of defining functions, they should define properties with function types. Those properties should represent the same functions as in the FunctionsClassic class. For instance, the add function should be represented by a property named add of type (Int, Int) -> Int. The behavior of those properties should also be identical to the behavior of functions from FunctionsClassic Implement classes with the following implementation of functional properties:
AnonymousFunctionalTypeSpecified - should define properties with explicit function types and define their values using anonymous functions. The types of parameters in those anonymous functions should be inferred.
AnonymousFunctionalTypeInferred - should define properties with inferred function types from anonymous function definitions that should be used to define values. The parameters of those anonymous functions should be explicitly typed.
LambdaFunctionalTypeSpecified - should define properties with explicit function types and define their values using lambda expressions. The types of parameters in those lambda expressions should be inferred. You should not use the implicit parameter it in this class.
LambdaFunctionalTypeInferred - should define properties with inferred function types from lambda expression definitions that should be used to define values. The parameters of those lambda expressions should be explicitly typed.
LambdaUsingImplicitParameter - should define properties with explicit function types and define their values using lambda expressions, just like LambdaFunctionalTypeSpecified, but it should use implicit parameter convention whenever possible.
This problem can either be solved in the below playground or you can clone kotlin-exercises project and solve it locally. In the project, you can find code template for this exercise in functional/base/Functional.kt. You can find there starting code and unit tests.
Once you are done with the exercise, you can check your solution here.
Playground
import org.junit.Test
import kotlin.reflect.KClass
import kotlin.reflect.typeOf
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class FunctionsClassic {
fun add(num1: Int, num2: Int): Int = num1 + num2
fun printNum(num: Int) {
print(num)
}
fun triple(num: Int): Int = num * 3
fun produceName(name: String): Name = Name(name)
fun longestOf(
str1: String,
str2: String,
str3: String,
): String =
maxOf(str1, str2, str3, compareBy { it.length })
}
data class Name(val name: String)
class AnonymousFunctionalTypeSpecified {
val add: (Int, Int) -> Int = fun(num1, num2) = num1 + num2
// TODO: Implement printNum, triple, produceName and longestOf properties using anonymous functions
// their type should be specified explicitly
// See add property for example
}
class AnonymousFunctionalTypeInferred {
val add = fun(num1: Int, num2: Int) = num1 + num2
// TODO: Implement printNum, triple, produceName and longestOf properties using anonymous functions
// their type should be inferred by compiler
// See add property for example
}
class LambdaFunctionalTypeSpecified {
val add: (Int, Int) -> Int = { num1, num2 -> num1 + num2 }
// TODO: Implement printNum, triple, produceName and longestOf properties using lambda functions
// their type should be specified explicitly
// See add property for example
}
class LambdaFunctionalTypeInferred {
val add = { num1: Int, num2: Int -> num1 + num2 }
// TODO: Implement printNum, triple, produceName and longestOf properties using lambda functions
// their type should be inferred by compiler
// See add property for example
}
class LambdaUsingImplicitParameter {
val add: (Int, Int) -> Int = { num1, num2 -> num1 + num2 }
// TODO: Implement printNum, triple, produceName and longestOf properties,
// just like in LambdaFunctionalTypeSpecified, but this time, whenever possible,
// use implicit parameter `it`.
}
class FunctionalTest {
@Test
fun `AnonymousFunctionalTypeSpecified has correct property signatures`() {
checkPropertySignatures(AnonymousFunctionalTypeSpecified::class)
}
@Test
fun `AnonymousFunctionalTypeSpecified has correct property behavior`() {
checkPropertyBehavior(AnonymousFunctionalTypeSpecified())
}
@Test
fun `AnonymousFunctionalTypeInferred has correct property signatures`() {
checkPropertySignatures(AnonymousFunctionalTypeInferred::class)
}
@Test
fun `AnonymousFunctionalTypeInferred has correct property behavior`() {
checkPropertyBehavior(AnonymousFunctionalTypeInferred())
}
@Test
fun `LambdaFunctionalTypeSpecified has correct property signatures`() {
checkPropertySignatures(LambdaFunctionalTypeSpecified::class)
}
@Test
fun `LambdaFunctionalTypeSpecified has correct property behavior`() {
checkPropertyBehavior(LambdaFunctionalTypeSpecified())
}
@Test
fun `LambdaFunctionalTypeInferred has correct property signatures`() {
checkPropertySignatures(LambdaFunctionalTypeInferred::class)
}
@Test
fun `LambdaFunctionalTypeInferred has correct property behavior`() {
checkPropertyBehavior(LambdaFunctionalTypeInferred())
}
@Test
fun `LambdaUsingImplicitParameter has correct property signatures`() {
checkPropertySignatures(LambdaUsingImplicitParameter::class)
}
@Test
fun `LambdaUsingImplicitParameter has correct property behavior`() {
checkPropertyBehavior(LambdaUsingImplicitParameter())
}
private fun checkPropertySignatures(
classToCheck: KClass<*>,
expectLongestOf: Boolean = true,
) {
val c = classToCheck.members
val properties = mutableMapOf(
"add" to typeOf<(Int, Int) -> Int>(),
"printNum" to typeOf<(Int) -> Unit>(),
"triple" to typeOf<(Int) -> Int>(),
"produceName" to typeOf<(String) -> Name>(),
)
if (expectLongestOf) {
properties += "longestOf" to typeOf<(String, String, String) -> String>()
}
for ((propertyName, propertyType) in properties) {
val propertyReference = c.find { it.name == propertyName }
assertNotNull(propertyReference) { "Property $propertyName is missing" }
assertEquals(propertyType, propertyReference.returnType, "Property $propertyName has wrong type")
}
}
@Suppress("UNCHECKED_CAST")
private fun <T: Any> checkPropertyBehavior(
instance: T,
expectLongestOf: Boolean = true,
) {
val members = instance::class.members
val add = members.find { it.name == "add" }!!
assertEquals(3, (add.call(instance) as (Int, Int) -> Int)(1, 2))
assertEquals(12, (add.call(instance) as (Int, Int) -> Int)(4, 8))
val printNum = members.find { it.name == "printNum" }!!
(printNum.call(instance) as (Int) -> Unit)(42)
val triple = members.find { it.name == "triple" }!!
assertEquals(9, (triple.call(instance) as (Int) -> Int)(3))
assertEquals(15, (triple.call(instance) as (Int) -> Int)(5))
val produceName = members.find { it.name == "produceName" }!!
assertEquals(Name("John"), (produceName.call(instance) as (String) -> Name)("John"))
assertEquals(Name("Jane"), (produceName.call(instance) as (String) -> Name)("Jane"))
if (expectLongestOf) {
val longestOf = members.find { it.name == "longestOf" }!!
assertEquals("abc", (longestOf.call(instance) as (String, String, String) -> String)("a", "ab", "abc"))
assertEquals("xyz", (longestOf.call(instance) as (String, String, String) -> String)("x", "xy", "xyz"))
}
}
}
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.