article banner

Exercise: Function types and literals

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")) } } }