Синтаксический метод для класса типов

avatar
Mojo
1 июля 2021 в 20:42
51
2
1

У меня есть следующая реализация простого класса типов:

sealed trait Foo

object Foo {
  case object A extends Foo
  case object B extends Foo
}

sealed trait Baz

object Baz {
  case object C extends Baz
  case object D extends Baz
}

trait TC[C <: Foo, T <: Baz] {
  val data: String
}

object instances {

  implicit val a: TC[Foo.A.type, Baz.C.type] = new TC[Foo.A.type, Baz.C.type] {
    val data = "some data"
  }

  implicit val b: TC[Foo.A.type, Baz.D.type] = new TC[Foo.A.type, Baz.D.type] {
    val data = "some other data"
  }

}

object syntax {
  
  def getData[A <: Foo](b: Baz): String =
    b match {
      case Baz.C => implicitly[TC[A, Baz.C.type]].data
      case Baz.D => implicitly[TC[A, Baz.D.type]].data
    }

}


Я получаю следующую ошибку компиляции:

could not find implicit value for parameter e: TC[A,Baz.C.type]
case Baz.C => implicitly[TC[A, Baz.C.type]].data

Разве нельзя написать такую ​​полиморфную функцию и разрешить экземпляр класса типов?

Источник
HTNW
1 июля 2021 в 20:46
1

getData[Foo.B.type](Baz.C) = ???, getData[Foo](Baz.C) = ???, getData[Foo.A.type with Foo.B.type](Baz.C) = ???, getData[Nothing](Baz.C) = ???, getData[Null](Baz.C) = ???, getData[Foo with Seq[Int]](Baz.C) = ???. Итак, так многое может пойти не так.

Luis Miguel Mejía Suárez
1 июля 2021 в 20:50
0

Идея typeclass состоит в том, чтобы избежать совпадений с этими шаблонами. Возможно, было бы полезно, если бы вы могли более подробно описать свою реальную проблему.

Mojo
1 июля 2021 в 20:59
0

@LuisMiguelMejíaSuárez Я показал простой пример, так как думал, что это будет самый простой способ понять проблему. По сути, я хочу предоставить некоторый метод, который возвращает правильное значение в зависимости от типа Foo и типа Baz, который его вызывает.

Mojo
1 июля 2021 в 21:01
0

так что в основном вы говорите, что механизм typeclass не должен использоваться с ADT

Luis Miguel Mejía Suárez
1 июля 2021 в 21:14
1

Проблема в том, что вы хотите, чтобы тип B был действительно известен во время выполнения, верно? - В любом случае, основная проблема сейчас заключается в том, что ваши экземпляры не входят в область действия и что компилятор не может знать, что будет экземпляр для любого T <: Foo.

Mojo
1 июля 2021 в 21:15
0

Да все верно

Ответы (2)

avatar
Luis Miguel Mejía Suárez
1 июля 2021 в 21:28
2

Вероятно, вы хотите что-то вроде этого

trait TC[T <: Foo] {
  def getData(b: Baz): String
}

object TC {
  def getData[T <: Foo](b: Baz)(implicit ev: TC[T]): String =
    ev.getData(b)

  implicit final val TCA: TC[A] = {
    case C => "Data 1"
    case D => "Data 2"
  }

  // More instances.
}

Сообщите мне, если это не сработает по какой-либо причине.
У меня могут быть некоторые синтаксические ошибки, но я надеюсь, что идея ясна

Mojo
1 июля 2021 в 22:26
0

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

Luis Miguel Mejía Suárez
1 июля 2021 в 22:39
0

@Mojo да, у вас может быть два параметра типа, но поскольку ваш второй тип будет зависеть от значения времени выполнения, вам нужно будет выполнить сопоставление с шаблоном. Я просто думаю, что эта конструкция проще.

Mojo
1 июля 2021 в 22:51
0

Я опубликовал возможное решение, но я понятия не имею, как оно работает. Я не мог обновить свой исходный пост, и ТАК не позволил бы мне, поэтому вместо этого добавил его в качестве ответа. Как это работает??

Mojo
6 июля 2021 в 11:44
0

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

avatar
Mojo
1 июля 2021 в 22:50
0

Это работает, но я понятия не имею как??

sealed trait Foo

object Foo {
  case object A extends Foo
  case object B extends Foo
}

sealed trait Baz

object Baz {
  case object C extends Baz
  case object D extends Baz
}

trait TC[C <: Foo, B <: Baz] {
  val data: String
}

object TC {

  implicit val a: TC[Foo.A.type, Baz.C.type] = new TC[Foo.A.type, Baz.C.type] {
    val data = "some data"
  }

  implicit val b: TC[Foo.A.type, Baz.D.type] = new TC[Foo.A.type, Baz.D.type] {
    val data = "some other data"
  }

}

object syntax {

  def getData[A <: Foo, B <: Baz](b: B)(implicit ev: TC[A, B]): String = implicitly[TC[A, B]].data

}

syntax.getData(Baz.D) // returns "some other date"
Mojo
1 июля 2021 в 22:52
0

Я даже не сказал ему, какой Foo он должен использовать для поиска экземпляра класса типов, поэтому не уверен, как это работает ???

Mojo
1 июля 2021 в 22:56
1

это потому, что есть только экземпляр с Baz.D, поэтому он выбирает его - если их два, то это приводит к неоднозначным имплицитам - ДЕРЬМО!

Luis Miguel Mejía Suárez
1 июля 2021 в 22:59
0

Компилятор всегда слишком старается компилировать, поэтому в этом случае он просто делает вывод, что сработало. Как и что и является ли оно детерминированным или нет, находится за пределами моего знания. Но в целом это вообще не сработает, потому что вам нужно указать оба параметра типа, что противоречит цели передачи значения типа Baz, если вы можете указать оба типа во время компиляции, а не во время выполнения, тогда да, это работает, Я бы просто использовал ev напрямую, а не вызывал его снова с помощью implicitly

Mojo
1 июля 2021 в 23:31
0

Это одна из вещей, которые я ненавижу в scala — правила неявного разрешения. Я ни за что не собираюсь тратить все это время на изучение всех этих правил и попытки угадать, что компилятор собирается делать с моим кодом.

Luis Miguel Mejía Suárez
1 июля 2021 в 23:52
0

На самом деле очень просто, если вы следуете хорошим рекомендациям по дизайну. Неявные значения разрешаются во время компиляции и основаны на типах.