How To Implement TriFunction in Kotlin?

In this article, I will show you how to implement your custom TriFunction in Kotlin and how SAM conversions, and lambdas can help us with it.
Image is a thumbnail for the post about Kotlin TriFunction and consist of Kotlin logo in the foreground and a bunch of people in the background.

1. Introduction

Hi! If you’ve ever been wondering how to implement a custom Trifunction in Kotlin, then you just came to the right place.

In this, short article we will cover the following:

  • what is a TriFunction?
  • what is a functional interface and how can we implement it in Kotlin?
  • how can we utilize SAM conversions to make our code more concise?
  • and finally- a slightly different approach, which may be better in some cases.

Video Tutorial

If you prefer video content, then check out my video:

If you find this content useful, please leave a subscription  😉

2. What is a Functional Interface?

Let’s start with defining what exactly is a functional interface.

Well, it is nothing else than an interface with exactly one abstract method.

In Kotlin, we can define it using the fun keyword:

fun interface MyFunctionalInterface {
  fun method()
  fun anotherMethod() // Compilation Error!

  // OK!
  fun nonAbstractMethod() {

  }
}

As we can see, such interfaces can have multiple non-abstract members.

However, the code won’t compile whenever we try to implement more than one abstract method:

Fun interfaces must have exactly one abstract method

3. TriFunction and Kotlin Implementation

The TriFunction interface is nothing else than a functional interface with three input parameters and one output.

To better understand it, let’s take a look at the following code:

fun interface CustomTriFunction<T, U, V, R> {
  fun someFun(t: T, u: U, v: V): R
}

In this example, we declare a generic functional interface CustomTriFunction with exactly one abstract function- someFun.

This function has 3 parameters of type T, U, and V, and returns a value of type R.

4. Test Function

Following, let’s declare an example test function:

fun <T, U, V, R> example(
  t: T,
  u: U,
  v: V,
  function: CustomTriFunction<T, U, V, R>,
): R {
  val result = function.someFun(t, u, v)
  // some logic here
  return result
}

As we can see, the example is a generic function, which we will invoke with 3 values, and as the last one, we will pass the implementation of CustomTriFunction.

And although this example is a bit trivial, we may want to use this strategy in real-life scenarios to keep our code open for extension and closed for modification.

5. Implementation No 1- Class

With all of that done, we can finally use the interface and implement the code responsible for invoking the example.

Let’s start with the most basic idea- adding a new class:

class CustomImplementation : CustomTriFunction<String, Long, Int, Double> {
  override fun someFun(t: String, u: Long, v: Int): Double {
    // some logic
    return 10.0
  }
}

The above code shouldn’t be surprising even if we are pretty new to Kotlin.

We implement the CustomTriFunction just like every generic interface and provide the body for someFun.

Nextly, the only thing we need to do is to create a new instance of our class and pass it as the last argument:

val customImplementation = CustomImplementation()
val result = exampleOne("", 0L, 0, customImplementation)

Of course, depending on our case the CustomImplementation could be a Kotlin object.

6. Idea 2- Anonymous Object

But sometimes, we don’t want to explicitly define a new class. Maybe we want to pass a very specific logic, which definitely won’t be used anywhere else in the code.

For such a one-time operation, we can use the anonymous object:

val anonymousObject = object : CustomTriFunction<String, Long, Int, Double> {
  override fun someFun(t: String, u: Long, v: Int): Double {
    return 10.0
  }
}
val result = exampleOne("", 0L, 0, anonymousObject)

As we can see, the instance of an anonymous object (also known as an anonymous class) looks almost the same, as in the previous example. With one, important difference- it does not have a name.

And as I mentioned previously- this approach may be really helpful for one-time use.

7. Implementation 3 (Better)- SAM Conversion

In Kotlin, functional interfaces are also known as Single Abstract Method (SAM) interfaces.

And instead of defining anonymous objects, we can make use of SAM conversions, which allow us to use lambda expressions instead:

val samConversionOne = CustomTriFunction { t: String, u: Long, v: Int -> 10.0 }
val resultOne = exampleOne("", 0L, 0, samConversionOne)

// or alternatively:
val samConversionTwo = CustomTriFunction<String, Long, Int, Double> { t, u, v -> 10.0 }
val resultTwo = exampleOne("", 0L, 0, samConversionTwo)

// or even:
val resultThree = exampleOne("", 0L, 0) { t, u, v -> 10.0 }

// Note: in Kotlin, if the last argument is a function,
// we can put it outside of round brackets- (). 

With a SAM conversion, Kotlin can convert any lambda expression whose signature matches the signature of the interface’s single method into the code, which dynamically instantiates the interface implementation.

Source: https://kotlinlang.org/docs/fun-interfaces.html#sam-conversions

And to put it simply, with this approach we can implement our TriFunction in a much more concise manner (which is often the case with Kotlin ;).

8. Approach 4- High-Order Function Without Interface

As the last example, let’s take a look at the high-order function.

In Kotlin, a high-order function is a function, which takes another function as an argument or returns a function.

So at the end of the day, instead of explicitly creating a custom TriFunction interface, we can define a parameter of function type:

fun <T, U, V, R> example(
  t: T,
  u: U,
  v: V,
  function: (T, U, V) -> R,
): R {
  val result = function.invoke(t, u, v) // or simply function(t, u, v)
  // some logic here
  return result
}

As we can see, the function parameter simply informs the compiler that we expect a function with three input arguments (T, U, V) and return type R.

And then, we can invoke it by passing a lambda to it:

val lambda = { t: String, u: Long, v: Int -> 10.0 }
val result = example("", 0L, 0, lambda)

// or even: 
val result = exampleTwo("", 0L, 0) { t, u, v -> 10.0 }

And this approach might be a great choice whenever we would like to have a TriFunction without explicitly defining a new interface.

9. Summary

And that’s all for this article about how to implement the Kotlin TriFunction interface in Kotlin.

I hope you enjoy my content and if you’d like to learn more about Kotlin and support my work, then I highly encourage you to check my Kotlin Handbook Course.

Share this:

Related content

Newsletter
Image presents 3 ebooks with Java, Spring and Kotlin interview questions.

Never miss any important updates from the Kotlin world and get 3 ebooks!

You may opt out any time. Terms of Use and Privacy Policy