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

String

s. 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]