Переменная scala - количество параметров, которые являются подклассами f-ограниченного типа.

В C++ я могу создать шаблон вариативной функции следующим образом:

#include <tuple>

// helper to loop over tuple
template <std::size_t I = 0, typename FuncT, typename... Args>
void for_each(std::tuple<Args...>& tuple, FuncT func) {
  func(std::get<I>(tuple));
  if constexpr (I + 1 < sizeof...(Args)) {
    for_each<I + 1, FuncT, Args...>(tuple, func);
  }
}

template <class A, class B, class Derived>
struct FBounded {
  auto foo() { return static_cast<Derived *>(this); }
  auto baz() { return static_cast<Derived *>(this); }
};

class Child1 : public FBounded<const char*, const char*, Child1> {};
class Child2 : public FBounded<bool, int, Child2> {};
class Child3 : public FBounded<double, const char*, Child3> {};

template <class... A, class... B, class... SubTypes>
static auto func(FBounded<A, B, SubTypes>... elems) {
  auto args = std::tuple(elems...);
  for_each(args, [](auto x) { x.foo()->baz(); });
}

int main() {
  auto c1 = Child1();
  auto c2 = Child2();
  auto c3 = Child3();

  func(c1, c2, c3);
}

Я хочу воссоздать это поведение в Scala. Вот что у меня есть на данный момент:

class FBounded[A, B, T <: FBounded[A, B, T]] {
  def foo(): T = this.asInstanceOf[T]
  def baz(): T = this.asInstanceOf[T]
}

class Child1 extends FBounded[Int, Double, Child1] {}
class Child2 extends FBounded[String, String, Child2] {}
class Child3 extends FBounded[Int, String, Child3] {}

def func(elems: Seq[FBounded[_, _, _]]) = {
    elems.foreach(_.foo.baz)
}


val c1 = new Child1()
val c2 = new Child2()
val c3 = new Child3()

func(c1, c2, c3)

Я получаю сообщение об ошибке:

error: value baz is not a member of _$3
elems.foreach(_.foo.baz)
              ^

Я считаю, что это как-то связано с тем, когда Scala заполняет типы заполнителей, но я не уверен.

2
0
110
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Тип FBounded[_, _, _] - это ярлык для экзистенциального типа, который чем-то похож на

FBounded[A, B, T] forSome { type A; type B; type T <: FBounded[A, B, T] }

и по какой-то причине компилятор отказывается вывести правильные f-границы для параметра типа T (по крайней мере, я не мог заставить его это сделать).

Я предполагаю, что это могло быть каким-то образом связано с причиной, по которой экзистенциальные типы отбрасываются в Dotty.

Вот обходной путь, который просто полностью избегает экзистенциальных типов:

class FBounded[A, B, T <: FBounded[A, B, T]] {
  self: T =>
  def foo: T = self
  def baz: T = self
  def wrap: FBE = new FBE {
    type a = A
    type b = B
    type t = T
    val value: t = self
  }
}

class Child1 extends FBounded[Int, Double, Child1] {}
class Child2 extends FBounded[String, String, Child2] {}
class Child3 extends FBounded[Int, String, Child3] {}

/** Wrapper for FBounded existential types */
abstract class FBE {
  type a
  type b
  type t <: FBounded[a, b, t]
  val value: t
}

def func(elems: FBE*) = {
  elems.map(_.value.foo.baz)
}

val c1 = new Child1()
val c2 = new Child2()
val c3 = new Child3()

func(c1.wrap, c2.wrap, c3.wrap)

Вместо того, чтобы полагаться на экзистенциальный FBounded[_, _, _], он использует класс-оболочку FBE, который содержит длинные списки со всеми типами и всеми ограничениями. С FBEfunc, кажется, работает нормально:

def func(elems: FBE*) = {
  elems.map(_.value.foo.baz)
}

потому что это можно более явно записать как:

def funcMoreExplicit(elems: FBE*) = {
  elems.map(e => {
    val v: e.t = e.value
    val fooRes: e.t = v.foo
    val bazRes: e.t = fooRes.baz
    bazRes
  })
}

где мы можем использовать явный зависимый от пути тип e.t, предоставленный FBE.t для промежуточных результатов.

Это отличный ответ! Итак, если я понимаю, идея состоит в том, что нам нужно использовать вспомогательный класс для удаления типа F-Bounded родительского элемента, тогда компилятору не нужно будет использовать типы-заполнители?

Nik 10.08.2018 20:17

@Nik Нам нужна оболочка FBE именно для того, чтобы мы не потеряли все параметры типа - у нас есть a, b и t, и мы также знаем, что t <: FBounded[a, b, t], поэтому мы знаем все, что нужно знать о комбинации типов, и мы полностью контролировать эти типы. Когда мы пытаемся сделать это с безымянными типами подстановочных знаков, мы должны полагаться на компилятор и надеяться, что он выведет все необходимые ограничения для синтетических фиктивных типов _1, _2 и т. д. В этом случае компилятор по какой-то причине отказал. Итак, я просто взял на себя управление и заставил его скомпилировать с помощью PDT.

Andrey Tyukin 10.08.2018 20:22

Типы, зависящие от пути @Nik, - это своего рода кувалда Scala для решения всех проблем. В чем-то похоже на мощные шаблоны в C++. Как ни странно, в этом случае перевод кода с тяжелым шаблоном в код с тяжелым типом, зависящим от пути, работает очень хорошо.

Andrey Tyukin 10.08.2018 20:23

Ах. Таким образом, мы не теряем типы, поскольку они сохраняются как параметры типа в оболочке. Спасибо!

Nik 11.08.2018 21:20

Другие вопросы по теме