```// K is used to ensure that FunctorIn and Funct...
# arrow
y
Copy code
// K is used to ensure that FunctorIn and FunctorOut conform to the same higher-kinded type. It's in because imagine
// that you internally produce a Some for example that you want to coerce to an Option, this allows you to do so safely
interface FunctorIn<in KA, out A, in K> {
  fun <B, KB> KA.fmap(fo: FunctorOut<B, KB, @UnsafeVariance K>, mapper: (A) -> B): KB
}

interface FunctorOut<in B, out KB, in K> {
  fun K.toK(): KB // this is just used for type safety and to push unsafe conversions to the XFunctorOutImpl
}

interface OptionFunctorIn<out A> : FunctorIn<Option<@UnsafeVariance A>, A, Option<Any?>>

// For a class with an out type param, a K value using Any? should be used just like below.
// For a class with an in type param, a K value using Nothing should be used.
// For an invariant class, use Any? or Nothing or Unit even and unsafely cast that to KB
interface OptionFunctorOut<in B> : FunctorOut<B, Option<@UnsafeVariance B>, Option<Any?>>

// Can also use an inline class here possibly but whatever
sealed class Option<out A> {
  inline fun <B> map(mapper: (A) -> B): Option<B> = when (this) {
    is Some -> Some(mapper(a))
    is None -> None
  }
}

data class Some<A>(val a: A) : Option<A>()
object None : Option<Nothing>()
object OptionFunctorInImpl : OptionFunctorIn<Any?> {
  // Functor out should most likely be OptionFunctorOutImpl, but we can't just assume that it'll be that and we can't
  // require this subclass to only accept OptionFunctorOut interface instances because that breaks the contract of
  // FunctorIn, and so we instead use K to indicate that this FunctorOut knows how to transform our K to a KB safely.
  override fun <B, KB> Option<Any?>.fmap(fo: FunctorOut<B, KB, Option<Any?>>, mapper: (Any?) -> B) =
    with(fo) { map(mapper).toK() }
}

object OptionFunctorOutImpl : OptionFunctorOut<Any?> {
  override fun Option<Any?>.toK(): Option<Any?> {
    return this
  }
}

inline fun <A> optionFunctorIn() = OptionFunctorInImpl as OptionFunctorIn<A>
inline fun <B> optionFunctorOut() = OptionFunctorOutImpl as OptionFunctorOut<B>
fun main() {
  // Note that some of these options' types are Some, and yet they still work with the normal OptionalFunctorX implementation
  val optionLife = Some(42)
  val optionExistence: Option<Any> = None
  val optionName = Some("name")
  val optionUnit = Some(Unit)
  println("hello world")
  // Using with to emulate the @with(value) decorator
  with(optionFunctorIn<Any>()) { // This has to be first because subtypes need to come at the very end
    with(optionFunctorIn<Unit>()) {
      with(optionFunctorIn<String>()) {
        with(optionFunctorIn<Int>()) {
          with(optionFunctorOut<String>()) OFS@{
            with(optionFunctorOut<Int>()) {
              // All of these require 2 receivers and so they can't be 100% prototyped right now but they should work as expected
              // Use Ctrl+Shift+P in IDEA to check the types of both the its and the return types of fmap and performMap
              println(optionLife.fmap(this@OFS) { it.toString() + " is a Some" })
              println(optionExistence.fmap(this@OFS) { it.toString() + " is a Some" })
              println(optionName.fmap(this@OFS) { it.toString() + " is a Some" })
              println(optionUnit.fmap(this@OFS) { it.toString() + " is a Some" })
              println(performMap(this@OFS, optionLife))
              println(performMap(this, optionLife))
              // Note that this performMap just needs a higher kinded container that has a CharSequence and not 
              // specifically a String, but because the types are declared with the proper variance it's automatically
              // inferred by the compiler that this String value will satisfy the CharSequence constraint
              println(performMap(this@OFS, optionName))
            }
          }
        }
      }
    }
  }
}

fun <KA, KB, K> FunctorIn<KA, CharSequence, K>.performMap(fo: FunctorOut<String, KB, K>, ka: KA): KB {
  return ka.fmap(fo) { it.toString() + " is a CharSequence" }
}

@JvmName("fmapIntToString")
fun <KA, KB, K> FunctorIn<KA, Int, K>.performMap(fo: FunctorOut<String, KB, K>, ka: KA): KB {
  return ka.fmap(fo) { it.toString() + " is an Int" }
}

@JvmName("fmapInt")
fun <KA, KB, K> FunctorIn<KA, Int, K>.performMap(fo: FunctorOut<Int, KB, K>, ka: KA): KB {
  return ka.fmap(fo) { it + 5 }
}