Public extension methods in objects and same-package imports


#1

I have the following:

package mypkg.util

object SequenceExt {
    fun <T : Any> Sequence<T?>.takeUntilNull(): Sequence<T> {
        // Unchecked cast, oh well, it's erased
        return this.takeWhile({ it != null }) as Sequence<T>
    }
}

First question: So I noticed I cannot import * from this in a separate package (e.g. import mypkg.util.SequenceExt.*) because I get “Cannot import-on-demand from object ‘SequenceExt’” in the IDE. Why is this? If I choose to organize my extensions inside an object instead of at the package level (I don’t want to clutter up “util” and don’t want a new package), do I have to manually import each method (e.g. “import mypkg.util.SequenceExt.takeUntilNull”)? This makes it tough for IDE discovery.

Second part: I am having trouble using this extension from the same package. In addition to the code above, I have the following in an adjacent file:

package mypkg.util

import SequenceExt.takeUntilNull

object CollectionExt {
    fun <T : Any> Collection<T?>.takeUntilNull(): Collection<T> {
        return this.asSequence().takeUntilNull().toList()
    }
}

But I get an IDE error on the import. Is this is a bug or is there something I am misunderstanding about singleton static imports in the same package that prevents it from happening?

Also, kinda third thing: I am really just needing a Scala-like collect (i.e. map + filter in the same iteration) and lazy take. Is my approach ideal (even though I am casting to non-null type param to avoid an iteration knowing I can save it with type erasure)?

Thanks for the help (sorry for several forum posts, I am trying to build a project and hitting a lot of road blocks).


#2

You should try using something like this.

/**
 * Created by abubacker.siddik
 */
object SequenceExt {

    fun <T : Any> Sequence<T?>.takeUntilNull(): Sequence<T> {
        // Unchecked cast, oh well, it's erased
        return this.takeWhile({ it != null }) as Sequence<T>
    }

    fun <T : Any> Collection<T?>.takeUntilNull(): Collection<T> {
        return this.asSequence().takeUntilNull().toList()
    }

    fun getList(list: List<String?>) : Collection<String>{
        return (list as Collection<String>).takeUntilNull()
    }
}

fun main(args: Array<String>) {
    println(SequenceExt.getList(listOf("1", "2", "3"))) //[1, 2, 3]
    println(SequenceExt.getList(listOf("1", "2", null))) //[1, 2]
}

i dont think you can use functions declared in an object in import statement.


#3

Or simply use collections to invoke takeUntilNull() function like this.

  fun getList(col: Collection<String?>) : Collection<String>{
        return col.takeUntilNull()
  }

#4

First of all, objects are not intended to be used for organizing code; packages are. If you put an extension function into an object, it will have an extra parameter for the object instance, which is somewhat inefficient and makes it harder to use from Java.

Second, you can’t use import * with an object, because every object defines the standard methods toString(), equals() and hashCode(), and the star import would put them in scope, which isn’t what you want.

Third, an import statement always references a fully qualified name, starting with the root package.

Finally, don’t edit import statements manually; let the IDE take care of them. Alt-Enter is your friend.


#5

Thanks for the update. Forgive my naivete on some of this, I should have realized. Couple of notes:

  • I believe the import restriction should be mentioned in “packages” or “objects” docs. I see “all the accessible contents of a scope (package, class, object etc)” when describing a wildcard import though I assume this is an ambiguous use of the word “object” and you don’t actually mean singleton objects.
  • For newcomers, it might also be worth mentioning that imports are in no way relative to the current package
  • In the API docs, I would strongly recommend separating (and grouping) extension functions at the package level when compared to non-extension functions. One of my goals by using objects was to do logical grouping of extension functions to prevent the large scrollset at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/index.html.
  • Worth noting that extension methods in objects are not subject to alt+enter in the intellij IDE across classes. I can understand why not (the index space may be too large to be worth storing especially since it seems to be an anti-pattern to create non-package-level extension methods except if they are used in the same file/class).
  • When heading to the import section of the IDE and typing just the class name of a same-package/adjacent class, alt+enter does not help. In fact, I can’t see alt+enter helping turn any local class name to its FQCN when typing in the import section.

Thanks again for the help