[Feature request] "pure" modifier

The goal of functional programming is to avoid mutability and side effects. I think it would be nice to mark a method as “pure” and then, at compile time, the compiler will check if the function is indeed pure. A function is pure when it has no side effects. For instance:

This is a pure function. This will compile (because it only uses the variables from within its scope - the x):

pure fun isPrime(x: Int): Boolean = 
    (1..x).toList().filter { x%it==0 }.size==2

This is not a pure function. It will not compile (because it uses variables from outside of its scope):

pure fun fetchData(): List<SomeData> =
    SomeDataRepository.fetch()

Going further, it would be nice to also apply this to classes. For instance:

pure class SomeClass(
    val arg1: Int
) : KoinComponent
{
    val someRepository = SomeDataRepository()
    val someDB by inject<DB>()

    fun f()
    {
        //ok, because we only use fields / state contained within this class's scope
        someRepository.save(arg1, someDB)

        //compile time error, because we access a state from the outside of the scope
        SomeOtherDataRepository.Instance.save(arg1, someDB)
    }
}

Why? Because in Kotlin and Java, such purity can be achieved only by applying a set of best practices. It is not checked at compile time.

How?

  1. As a language feature: What do you think?
  2. Or as a compiler plugin. I know that Google is working on Jetpack Compose. It’s an impressive compiler plugin. So perhaps, this feature can be implemented as a third party plugin, right?
  3. Or as a lint check / static code analysis tool?
4 Likes

There hava been a few discussions about thise here alerady. Just search “pure function” in this forum and you get a lot, so let me summarize (from memory mostly).
I don’t remember any real oposition to the idea, however there are a few problems. Mainly implementing this isn’t as easy as you might immagine. There are problems with java interop and the fact that there aren’t any real immutable data types on the jvm (except for primitives). All functions/classes accessed by a pure function/class would also need to be pure which would mean no access to any java libraries and also no open classes since pure can’t be guranteed through inheritance. This problem alone would mean that your isPrime function would no longer be pure. You are using at least 2 classes that arn’t pure (IntRange and List) and in the case of List will never be pure.
To solve those issues you probably need to first add a system for immutable data to kotlin.

Don’t get me wrong, this is not immpossible to solve, but it will take a well thought through design and probably quite some time. The question is whether it solves a big enough problem to warrant the extra work.
I personally think that this might be the case. Pure functions allow for many optimizations and help avoid bugs. In the end this would definetly need a KEEP before it can be implemented. A good place to start is probably looking through the other discussions here and looking at the issues identified in them. Another thing that would help is a list of concrete examples that show the improvements pure would bring to kotlin.

This would definetly need to be part of kotlin. I don’t think a compiler plugin would be much help here unless you want to extend the problems of java interop to the entire kotlin ecosystem.

1 Like

Thanks for your reply.

I understand this is hard to implement. But I was only thinking about superficial purity checks:

  • any function marked as pure should use only variables within its scope (arguments, local variables)
  • any class marked as pure should have only methods that use only variables within its scope (class fields, properties, method arguments, local variables)

Maybe a more realistic approach to have these checks would be via a lint check / static code analysis tool.

Actually, in the case that the compiler can know the concrete class of an instance (which is true in this case) it doesn’t matter that the function isn’t final as it will be able to determine the actual function called at compile time (and determine its purity).

I want to propose a change to this modifier.
I want to cut it in two modifiers:

  • clean, which prohibts accessing state outside of its scope. In it’s definition, it doesn’t about modification
  • pure, this would be an extension of clean, which prohibits the modification as well.

This way, we keep pure open if there is a better implementation for immutability.
At the same time, we already would lock down parts of our programs.

questions

  • What is your stand on var/val for your suggestion?
  • What about stateless utility-functions which could be accessible in a companion-object?
  • I don’t know much about pure, but saving something isn’t pure, right?

The compiler just needs to verify purity at compile time, it doesn’t need to enforce it at runtime. The JVM doesn’t understand generics either, but that didn’t prevent people from implementing them.

About Java interop, you can use the @Contract(pure = true) annotation from IntelliJ to mark a Java function as pure. Beyond that, pure Kotlin functions can be treated like regular functions in Java.

I agree that it would be almost impossible for the compiler to verify that (1..x).toList() is pure. But many Kotlin classes could be pure; inheritance is not a problem if the class is final. If an open or abstract class is pure, we could require that sub-classes must be pure as well.

That wouldn’t be very useful, since it doesn’t prevent side effects at all. It would even confuse people, because most people expect a function that’s called “pure” to be actually pure.

A better definition (IMO) of a pure class would be:

  • all properties must be val instead of var
  • all properties must be of pure types
  • all methods and getters must be pure
    • a function is pure if it doesn’t mutate any value and calls only pure functions
  • all inherited and delegated methods that aren’t abstract, must be pure
  • all inner classes and object expressions (anonymous inner classes) must be pure

Note that the companion object and nested classes don’t need to be pure, and extension methods also don’t need to be pure. However, all sub-classes must be pure.

2 Likes

This is the point where I see the most issues, because of java interop. I’m not talking about the JVM not understanding that the class is supposed to be pure. The problem already exists if someone is overriding a function of a pure class, because the java compiler won’t check for purity. I know that the same issue exists with nullable return/parameter types, but the kotlin compiler adds runtime checks to guard agaisnt this.
I’m not saying that this makes it impossible, I just want to ensure that this is considered. Maybe a pure class should juse be final as well, so no open or abstract pure classes. This restriction could also be lifted later on when we understand this subject better.
Other than that I fully agree with your definition of a pure class.