https://kotlinlang.org logo
#getting-started
Title
# getting-started
l

Liudvikas Sablauskas

01/05/2022, 1:10 PM
Hello everyone. This is not really a question for #getting-started, but I couldn't find a better place (if there is, tell me). 3 years ago I held an internal company contest with 7 Kotlin edge case / oddity puzzles. Today we decided to revisit and solve them. We solved all except one. Can someone help us crack this nut?
Copy code
/**
 * Task: Make line `DisNonNullable.nonNull()` not compile
 * Clarification: `DisNonNullable.nonNull()` will always have a non-null value, so it makes sense to not allow that method
 * Clarification: Everything else has to compile just as before, only `DisNonNullable.nonNull()` should fail
 * Clarification: The non-generic parameter type String is chosen only as an example. This hardcoded type is not the focus of the task
 * Restrictions: Mustn't modify main() method, DisNullable and DisNotNullable classes
 */

fun main(args: Array<String>) {
    DisNullable.nonNull()
    DisNonNullable.nonNull()
}

abstract class GenericMapper<T>  {
    abstract fun map(string: String): T

    fun nonNull(): GenericMapper<T> {
        val delegate = this
        return object : GenericMapper<T>() {
            override fun map(string: String): T {
                return delegate.map(string)
            }
        }
    }
}

object DisNullable : GenericMapper<String?>() {
    override fun map(string: String) = string
}

object DisNonNullable : GenericMapper<String>() {
    override fun map(string: String) = string
}
r

Rob Elliot

01/05/2022, 1:23 PM
It breaks the rules, but this is what I'd do:
Copy code
fun main(args: Array<String>) {
  DisNullable.nonNull()
  DisNonNullable.nonNull()
}

// Internal so that there are only two immediate subtypes, but it isn't sealed, so other subtypes of
// those immediate subtypes can be defined in other packages.
abstract class GenericMapper<T> internal constructor() {
  abstract fun map(string: String): T
}

abstract class NullableGenericMapper<T> : GenericMapper<T>()  {

  fun nonNull(): GenericMapper<T> {
    val delegate = this
    return object : GenericMapper<T>() {
      override fun map(string: String): T {
        return delegate.map(string)
      }
    }
  }
}

abstract class NonNullableGenericMapper<T : Any> : GenericMapper<T>()

object DisNullable : NullableGenericMapper<String?>() {
  override fun map(string: String) = string
}

object DisNonNullable : NonNullableGenericMapper<String>() {
  override fun map(string: String) = string
}
Hang on - don't think I understood the purpose of
fun nonNull()
when I wrote that. Guess in my version it should return
NonNullableGenericMapper
.
l

Liudvikas Sablauskas

01/05/2022, 1:39 PM
It's incorrect, DisNullable and DisNonNullable should extend the same class
But I definitely remember that there was a solution, exactly according to these rules
r

Rob Elliot

01/05/2022, 1:41 PM
They do. Just with an extra layer of inheritance in the middle.
l

Liudvikas Sablauskas

01/05/2022, 1:42 PM
And that is not allowed 😄
Copy code
object DisNullable : GenericMapper<String?>() {
    override fun map(string: String) = string
}

object DisNonNullable : GenericMapper<String>() {
    override fun map(string: String) = string
}
This should not change at all
r

Rob Elliot

01/05/2022, 1:42 PM
Why?
l

Liudvikas Sablauskas

01/05/2022, 1:43 PM
This is a puzzle, of course in practice you wouldn't care either way 😄
r

Rob Elliot

01/05/2022, 1:44 PM
But even puzzles need to justify their requirements.
Particularly if they are puzzles without a known solution. It's one thing to say "The constraints may seem arbitrary, but they can be met" as an intellectual challenge. "The constraints are arbitrary and it may be impossible to meet them" is silly - why would anyone waste their time trying to meet arbitrary constraints that are impossible to meet?
l

Liudvikas Sablauskas

01/05/2022, 1:46 PM
"The constraints are arbitrary and it may be impossible to meet them
No, I just said that
But I definitely remember that there was a solution, exactly according to these rules
r

Rob Elliot

01/05/2022, 1:47 PM
Ah, missed that in the middle of the thread. Fair enough.
👍 2
FWIW though on the illegal version it is possible to constrain the nullable type to a non-nullable one:
Copy code
abstract class NullableGenericMapper<E : Any> : GenericMapper<E?>() {
  fun nonNull(): NonNullableGenericMapper<E> = TODO()
}

object DisNullable : NullableGenericMapper<String>() {
  override fun map(string: String): String? = null
}
j

Jason5lee

01/05/2022, 2:34 PM
Copy code
Clarification: `DisNonNullable.nonNull()` will always have a non-null value, so it makes sense to not allow that method
It does not make sense to me.
r

Rob Elliot

01/05/2022, 2:46 PM
I think the intention of
nonNull
is to transform a
GenericMapper<String?>
into a
GenericMapper<String>
so that when you call
map
on it you get back a
String
not a
String?
. So calling
GenericMapper<String>.nonNull()
is pointless - it would effectively return itself - and the idea is to make it not compile so that people don't accidentally call it.
l

Liudvikas Sablauskas

01/05/2022, 2:49 PM
Yes, you are correct, that's the reason
r

Rob Elliot

01/05/2022, 3:07 PM
It's trivial of course once you remember the existence of extension functions:
Copy code
fun main(args: Array<String>) {
  DisNullable.nonNull()
  DisNonNullable.nonNull()
}

abstract class GenericMapper<T>  {
  abstract fun map(string: String): T
}

fun <T> GenericMapper<T?>.nonNull(): GenericMapper<T> {
  val delegate = this
  return object : GenericMapper<T>() {
    override fun map(string: String): T {
      return delegate.map(string) ?: TODO("handle null")
    }
  }
}

object DisNullable : GenericMapper<String?>() {
  override fun map(string: String) = string
}

object DisNonNullable : GenericMapper<String>() {
  override fun map(string: String) = string
}
❤️ 1
l

Liudvikas Sablauskas

01/05/2022, 3:25 PM
Wow, that's a nice solution and it's definitely valid! Though I still can't remember if that's also the solution that I used 3 years ago, but it very well might be. Thanks!