【问题标题】:Scala variable number of parameters that are sub-classes of an F-Bounded typeScala 可变数量的参数是 F 有界类型的子类
【发布时间】:2019-01-18 04:35:10
【问题描述】:

在 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 填写占位符类型有关,但我不确定。

【问题讨论】:

    标签: c++ scala types generic-programming f-bounded-polymorphism


    【解决方案1】:

    FBounded[_, _, _]-type 是一种存在类型的快捷方式,看起来有点像

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

    无论出于何种原因,编译器都拒绝为类型参数 T 推断正确的 f 边界(至少我无法做到这一点)。

    我想这可能以某种方式与existential types are dropped in 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
      })
    }
    

    我们可以使用FBE.t 提供的显式路径相关类型e.t 来获取中间结果。

    【讨论】:

    • 这是一个很好的答案!所以如果我理解的话,我们的想法是我们需要使用一个帮助类来擦除 F-Bounded 父级的类型,那么编译器就不需要使用占位符类型了吗?
    • @Nik 我们需要FBE 包装器,以便我们不会丢失所有类型参数 - 我们有abt在那里,我们也知道t &lt;: FBounded[a, b, t],所以我们知道所有关于类型组合的知识,并且我们完全控制了这些类型。当我们尝试使用未命名的通配符类型来执行此操作时,我们必须依赖编译器,并希望它能够推断出合成虚拟类型_1_2 等的所有必要约束。在这种情况下,编译器失败了因为某些原因。所以,我只是接管了控制,并强制它使用 PDT 进行编译。
    • @Nik 路径依赖类型有点像 Scala 解决所有问题的大锤。有点像 C++ 中强大的模板。有趣的是,在这种情况下,将模板繁重的代码转换为依赖于路径的类型繁重的代码效果很好。
    • 啊。所以我们不会丢失类型,因为它们在包装器中保存为类型参数。谢谢!
    猜你喜欢
    • 2015-09-14
    • 2018-05-20
    • 1970-01-01
    • 2015-06-03
    • 2016-09-22
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 2019-02-14
    相关资源
    最近更新 更多