Hello everyone. This is not really a question for ...
# getting-started
l
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
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
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
They do. Just with an extra layer of inheritance in the middle.
l
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
Why?
l
This is a puzzle, of course in practice you wouldn't care either way 😄
r
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
"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
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
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
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
Yes, you are correct, that's the reason
r
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
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!