How do I combine multiple scope functions for concise Kotlin code?

In Kotlin, scope functions (like let, apply, also, run, and with) provide a way to execute a block of code within the context of an object. Combining them allows writing concise and readable code. Here’s how you can effectively use and combine them:

Example: Combining let and apply

Suppose you have an object that you need to both configure and process:

val result = myObject.apply {
    // Configure the object
    property1 = "value1"
    property2 = "value2"
}.let {
    // Use the configured object and transform it if needed
    it.someTransformingFunction()
}

Explanation:

  1. apply is used to mutate the object (configuration stage).
  2. let provides access to the configured object and performs further operations.

Example: Combining with and run

If you need to perform several actions on an object, including calculations:

val result = with(myObject) {
    // Perform actions on the object
    doAction1()
    doAction2()

    // Return something for further use
    this
}.run {
    // Execute more actions in the context of the returned object
    performAdditionalTask()
    finalResult()
}

Explanation:

  1. with is used as a concise way to operate on an object without changing its state, returning the object itself or another value.
  2. run is then used for further actions or transformations.

Example: Combining let and also

To perform logging or debugging while processing data:

val result = sourceString.let { input ->
    input.trim()
}.also { trimmed ->
    println("Trimmed string: $trimmed")
}

Explanation:

  1. let is used to create a pipeline where the string is transformed.
  2. also is used for side-effects, such as logging or debugging, without altering the object.

Example: Combining in Nested Chains

For a more complex scenario where multiple scope functions are needed:

val result = myObject.apply {
    property1 = "value1"
    property2 = "value2"
}.let {
    // Transform the configured object
    it.someTransformingFunction()
}.also {
    // Log the transformation
    println("Transformed object: $it")
}

This approach allows you to configure an object, transform it, and log its state in a single elegant chain.


General Guidelines

  • Use apply when you want to configure or initialize an object.
  • Use let when you want to execute a block of code with the object as a parameter and transform/compute something.
  • Use also when you need to perform a side effect (e.g., logging) while keeping the object unchanged.
  • Use run and with for blocks of code where you are mostly operating on the object and possibly returning a value.

By properly combining these scope functions, you achieve concise, clean, and functional Kotlin code!

How do I modified the value of LocalDate and LocalTime object?

The easiest way to modify the value of a LocalDate, LocalTime or LocalDateTime object is to use the with() method of the corresponding object. These methods will return a modified version of the object, it doesn’t change the attribute of the original object. All the methods, like withYear(), withDayOfMonth() or the with(ChronoField) of the LocalDate object will return a new object with the modified attribute.

With the LocalTime object you can use the withHour(), withMinute(), withSecond() or the more generic with(ChronoField) method to modified the attribute of a LocalTime object. You can also modified a LocalDateTime object using these with() method. Let’s see the example in the code snippet below.

package org.kodejava.datetime;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;

public class ManipulatingDateTime {
    public static void main(String[] args) {
        LocalDate date1 = LocalDate.of(2021, 4, 21);
        System.out.println("date1 = " + date1);
        LocalDate date2 = date1.withYear(2020);
        System.out.println("date2 = " + date2);
        LocalDate date3 = date2.withDayOfMonth(10);
        System.out.println("date3 = " + date3);
        LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 12);
        System.out.println("date4 = " + date4);

        LocalTime time1 = LocalTime.of(1, 5, 10);
        System.out.println("time1 = " + time1);
        LocalTime time2 = time1.withHour(6);
        System.out.println("time2 = " + time2);
        LocalTime time3 = time2.withMinute(45);
        System.out.println("time3 = " + time3);
        LocalTime time4 = time3.with(ChronoField.SECOND_OF_MINUTE, 25);
        System.out.println("time4 = " + time4);

        LocalDate now1 = LocalDate.now();
        System.out.println("now1 = " + now1);
        LocalDate now2 = now1.plusWeeks(1);
        System.out.println("now2 = " + now2);
        LocalDate now3 = now2.minusMonths(2);
        System.out.println("now3 = " + now3);
        LocalDate now4 = now3.plus(15, ChronoUnit.DAYS);
        System.out.println("now4 = " + now4);
    }
}

The output of this code snippet are:

date1 = 2021-04-21
date2 = 2020-04-21
date3 = 2020-04-10
date4 = 2020-12-10
time1 = 01:05:10
time2 = 06:05:10
time3 = 06:45:10
time4 = 06:45:25
now1 = 2021-11-22
now2 = 2021-11-29
now3 = 2021-09-29
now4 = 2021-10-14

These with() methods is the counterpart of the get() methods. Where the get() methods will give you the value of the corresponding LocalDate or LocalTime attribute, the with() method will change the attribute value and return a new object. It didn’t call set because the object is immutable, which means it value cannot be changed.

While with the with() method you can change the value of date time attribute in an absolute way using the plus() or minus() method can help you change the date and time attribute in a relative way. The plus() and minus() method allows you to move a Temporal back or forward a give amount of time, defined by a number plus a TemporalUnit, in this case we use the ChronoUnit enumeration which implements this interface.