You are currently viewing Potentially terrible idea to use extension functions inside a class as “pseudo-private”.

Potentially terrible idea to use extension functions inside a class as “pseudo-private”.

Sometimes I like to just fiddle around and think about what I can do, rather than what I should do. 😉

Here’s the motivating example. Let’s say I’m implementing some class. Inside that class I need to perform some operations on Strings. These are pretty specialized operations that are really only relevant to this class’s functionality. Here’s a skeleton of an example of what I would sometimes do:

class Foo() { fun doSomething() { // some logic val x = parseBookFile(); if (x.countPages() > 1000) { throw Exception("tl;dr") } // more logic } } private fun String.countPages() = TODO() // domain specific term doesn't belong public 

But, now you can’t test that countPages function by itself. You just have to test Foo.doSomething more thoroughly. If you do this too much you get combinatorial explosion in your test cases.

What if we did this instead?

class Foo() { fun doSomething() { // some logic val x = parseBookFile(); if (x.countPages() > 1000) { throw Exception("tl;dr") } // more logic } fun String.countPages() = TODO() // domain specific term doesn't belong public } 

Now the function String.countPages isn’t technically private, but outsiders can’t really use it unless they work kind of hard to get at it. The only way to access String.countPages is while inside class Foo‘s context. In other words, you’re either editing Foo‘s definition directly or you’re writing an extension function for Foo:

fun doAThing() { "abc".countPages() // compile error } fun Foo.doSomethingElse() { "abc".countPages() // here you can access it! } 

I haven’t actually used this anywhere yet, but I feel like it’s a nice middle ground between other options. Those other options being:

  • Keep the function private. Now you can’t test it.
  • Make the function public/internal. Now it “pollutes” the namespace when I’m almost positive that others should not use this function.
  • Make the function internal but make this whole class/functionality into a module, so you can test it, but still keep it private from users of your new “library”. This is clearly overkill for many cases.
  • Kotlin implements package-private. :p

The functionality itself doesn’t have to be an extension function, either, necessarily:

class Foo() { fun doSomething() { [snip[ } fun Foo.add(i: Int, j: Int): Int = TODO() } 

This will have the same effect of only allowing add to be accessed from an extension function on Foo. This form does give a compiler warning that the receiver isn’t used, though.

So, I don’t know. I just thought it was pretty cute and that I might share it. 😀

EDIT: Taken to its logical conclusion, I guess we can end up with something like:

class Foo() { fun doSomething() = with(FooContext) { // some logic val x = parseBookFile(); if (x.countPages() > 1000) { throw Exception("tl;dr") } // more logic } } object FooContext { fun String.countPages() = TODO() // domain specific term doesn't belong public } 

submitted by /u/ragnese
[link] [comments]