Способна ли scala выводить члены абстрактного типа конструктора типов?

Я пытаюсь извлечь члены абстрактного типа из неявных параметров (а-ля Shapeless), например

trait F[T] { type Out }

object F {
  type Aux[T, out] = F[T] { type Out = out }
}

def glhf[t, out](implicit f: F.Aux[t, out]): out = ???

Это прекрасно работает для любого вида извлечения (даже для сложных перекрестно-неявных переменных типа).

Однако, когда член абстрактного типа является конструктором типа, а не простым типом, компилятору не удается унифицировать переменную типа в точке вызова.

Я сделал небольшой тестовый пример, в котором произошла странная ошибка компиляции. Ошибка компилятора сама по себе не имеет особого смысла, поэтому мне интересно, является ли это ошибкой компилятора? Подробные сведения о сообщении об ошибке см. в примере кода.

Скомпилировано с scala-2.12.4, -Xlog-implicits для дополнительных сообщений и даже -Ypartial-unification на случай, если это будет проблемой.

incubator/Main.scala:

package incubator

object wat {

  /**
   * A "type class", "implicit evidence" type, etc...
   *
   * @tparam t just for looks, and facilitate
   *  the implicit resolution scenario
   */
  trait fo[t] {
    /**
     * An abstract type member THAT IS A TYPE CONSTRUCTOR
     */
    type f[_]
  }

  //
  // Types that will be used for `fo`'s abstract type `f[_]`
  //
  trait F1[t]
  trait F2[t]
  //
  // Couple of case for type class `fo`
  //
  trait loo
  implicit object loo extends loo with fo[loo] {
    type f[t] = F1[t]
  }
  //
  trait poo
  implicit object poo extends poo with fo[poo] {
    type f[t] = F2[t]
  }

  // Double checking, this compiles
  val w0 = implicitly[ fo[loo] ]
  val w1 = implicitly[ fo[poo] ]

  /**
   * *** PROBLEM HERE ***
   * 
   * A method call, in which the abstract TYPE CONSTRUCTOR type member
   * needs to be inferred by the compiler.
   *
   * This fails to be implicitly resolved, because the compiler
   * fails to instantiate the type parameters, (probably) because
   * it is unable to infer abstract type `f`. See further below
   * for the failed invocation.
   *
   */
  def fu0[t, in[_]](t: t)(
    implicit
    fo: fo[t] { type f[a] = in[a] }
  ): String = s"Hi $t: $fo"

  // These will work fine, since we explicitly set type param `in`
  val w2 = fu0[loo, F1](loo: loo)
  val w3 = fu0[poo, F2](poo: poo)

  // *** PROBLEM HERE ***
  // The following fails to compile
  val w4 = fu0(loo: loo) // type ascription for test simplification
  val w5 = fu0(poo: poo) // type ascription for test simplification

  //
  // Error message:
  //
  // (notice the "type f has one type parameter, but type in has one"
  //  part of the error)
  //
  // [info] .../incubator/Main.scala:64:15: poo is not a valid implicit value for incubator.wat.fo[incubator.wat.poo]{type f[a] = in[a]} because:
  // [info] type parameters weren't correctly instantiated outside of the implicit tree: inferred kinds of the type arguments (incubator.wat.poo.f[t]) do not conform to the expected kinds of the type parameters (type in).
  // [info] incubator.wat.poo.f[t]'s type parameters do not match type in's expected parameters:
  // [info] type f has one type parameter, but type in has one
  // [info]   val w5 = fu0(poo: poo) // type ascription for test simplification
  // [info]               ^
  // [info] .../incubator/Main.scala:64:15: incubator.this.wat.poo is not a valid implicit value for incubator.wat.fo[incubator.wat.poo]{type f[a] = in[a]} because:
  // [info] type parameters weren't correctly instantiated outside of the implicit tree: inferred kinds of the type arguments (incubator.wat.poo.f[t]) do not conform to the expected kinds of the type parameters (type in).
  // [info] incubator.wat.poo.f[t]'s type parameters do not match type in's expected parameters:
  // [info] type f has one type parameter, but type in has one
  // [info]   val w5 = fu0(poo: poo) // type ascription for test simplification
  // [info]               ^
  // [error] .../incubator/Main.scala:64:15: could not find implicit value for parameter fo: incubator.wat.fo[incubator.wat.poo]{type f[a] = in[a]}
  // [error]   val w5 = fu0(poo: poo) // type ascription for test simplification
  // [error]               ^
  // [error] two errors found
  // [error] (compile:compileIncremental) Compilation failed
  // [error] Total time: 1 s, completed Oct 22, 2017 4:48:35 PM
  //

}

person Prikso NAI    schedule 22.10.2017    source источник


Ответы (1)


Используя следующее определение в fu0, вы в основном переделываете fo, поэтому компилятор не знает точно, что происходит.

implicit
fo: fo[t] { type f[a] = in[a] }

Вы уже определили fo, и с помощью этого статического трейта вам не придется использовать явную типизацию.

def fu0[t, in[_]]
    (t: t)
    (implicit fo: fo[t] ): String = s"Hi $t: $fo"

Это работает на приведенном примере, но я бы предположил, что в вашем реальном случае проблема лежит глубже во вложенных типах. Если моя оценка верна, предоставьте более конкретный тестовый пример, чтобы мы могли его изучить. Это довольно интересная тема.

Воспроизведение

Чистая установка с scalaVersion := "2.12.4"

package io.sosc

object Main {

    trait fo[t] {

        type f[_]
    }

    trait F1[t]
    trait F2[t]

    trait loo

    implicit object loo extends loo with fo[loo] {
        type f[t] = F1[t]
    }

    trait poo

    implicit object poo extends poo with fo[poo] {
        type f[t] = F2[t]
    }

    def fu0[t, in[_]]
        (t: t)
        (implicit fo: fo[t] ): String = s"Hi $t: $fo"


    def main( args: Array[ String ] ): Unit = {



        val w0 = implicitly[ fo[loo] ]
        val w1 = implicitly[ fo[poo] ]

        val w2 = fu0[loo, F1](loo: loo)
        val w3 = fu0[poo, F2](poo: poo)

        println( w2 )
        println( w3 )

        val w4 = fu0(loo: loo)
        val w5 = fu0(poo: poo)

        println( w4 )
        println( w5 )
    }
}

Результат:

Hi io.sosc.Main$loo$@465ba3d7: io.sosc.Main$loo$@465ba3d7
Hi io.sosc.Main$poo$@675b0a69: io.sosc.Main$poo$@675b0a69
Hi io.sosc.Main$loo$@465ba3d7: io.sosc.Main$loo$@465ba3d7
Hi io.sosc.Main$poo$@675b0a69: io.sosc.Main$poo$@675b0a69
person Dennis Tsoi    schedule 22.10.2017
comment
Спасибо за ответ, Деннис, но, как вы заметили, дело в том, что параметр типа in[_] повторно используется в сигнатуре метода. Ваше решение просто удаляет переменную типа из подписи. Вопрос требует уточнения, чтобы указать этот вариант использования. Еще раз спасибо. - person Prikso NAI; 22.10.2017
comment
Отметьте меня в комментарии, когда сделаете комплексный тест :) - person Dennis Tsoi; 22.10.2017