【问题标题】:Aux pattern with inherited types fails inference具有继承类型的辅助模式无法推理
【发布时间】:2019-03-10 16:08:15
【问题描述】:

我有一个复杂的玩具算法,我希望纯粹在类型级别表示:根据饮食要求为当天的一道菜选择修改。为卷积道歉,但我认为我们需要每一层才能到达我想要使用的最终界面。

我的代码存在问题,如果我们在基于另一个泛型类型的Aux-pattern-generated 类型上表达类型约束,则类型推断失败。

这些是餐点,实际上会有很多种比萨饼和多种基础餐点:

trait Pizza
trait CheeselessPizza extends Pizza

饮食要求:

sealed trait DietaryRequirement
trait Vegan extends DietaryRequirement

当日菜式分类:

sealed trait DishOfTheDay[Meal]

object DishOfTheDay {
  implicit val dishOfTheDay: DishOfTheDay[Pizza] = null
}

这将改变每天的膳食,独立于程序的其余部分。

ModifiedMeal 类型类,它接受膳食和饮食要求并生成满足要求的submeal。子类型在这里很重要:

// <: Meal is important
sealed trait ModifiedMeal[Meal, D <: DietaryRequirement] { type Mod <: Meal }

object ModifiedMeal {

  type Aux[Meal, D <: DietaryRequirement, Mod0 <: Meal] = ModifiedMeal[Meal, D] { type Mod = Mod0 }

  // Only one instance so far, Vegan Pizza = CheeselessPizza
  implicit val veganPizzaModifiedMeal: ModifiedMeal.Aux[Pizza, Vegan, CheeselessPizza] = null

}

这是为我们进行计算的最终类型类:

// Given a dietary requirement, give us a dish of the day which satisfies it
// if one exists
trait DishOfTheDayModification[Req <: DietaryRequirement] { type Out }

object DishOfTheDayModification {

  type Aux[Req <: DietaryRequirement, Out0] = DishOfTheDayModification[Req] { type Out = Out0 }

  // Find the dish of the day, then find a ModifiedMeal of it
  // <: Meal is important here so we pick up ONLY pizzas and not some other meal
  implicit def dishOfTheDayModification[Meal, Req <: DietaryRequirement, Mod <: Meal](
    implicit d: DishOfTheDay[Meal],
    impl: ModifiedMeal.Aux[Meal, Req, Mod]
  ): DishOfTheDayModification.Aux[Req, Mod] = null

}

这是测试:

object MealTesting {
  def veganDishOfTheDay[Mod](implicit d: DishOfTheDayModification.Aux[Vegan, Mod]): Mod = ???

  // Does not compile but it should
  veganDishOfTheDay: CheeselessPizza
}

问题是调用这个方法不能编译,但它应该

如果你复制整个程序但从生成的餐中删除&lt;: Meal 要求,它会编译。这又是整个事情,但“工作”:

trait Pizza
trait CheeselessPizza extends Pizza

sealed trait DietaryRequirement
trait Vegan extends DietaryRequirement

sealed trait DishOfTheDay[Meal]

object DishOfTheDay {
  implicit val dishOfTheDay: DishOfTheDay[Pizza] = null
}

sealed trait ModifiedMeal[Meal, D <: DietaryRequirement] { type Mod }

object ModifiedMeal {

  type Aux[Meal, D <: DietaryRequirement, Mod0] = ModifiedMeal[Meal, D] { type Mod = Mod0 }

  implicit val veganPizzaModifiedMeal: ModifiedMeal.Aux[Pizza, Vegan, CheeselessPizza] = null

}

trait DishOfTheDayModification[Req <: DietaryRequirement] { type Out }

object DishOfTheDayModification {

  type Aux[Req <: DietaryRequirement, Out0] = DishOfTheDayModification[Req] { type Out = Out0 }

  implicit def dishOfTheDayModification[Meal, Req <: DietaryRequirement, Mod](
    implicit d: DishOfTheDay[Meal],
    impl: ModifiedMeal.Aux[Meal, Req, Mod]
  ): DishOfTheDayModification.Aux[Req, Mod] = null

}

object MealTesting {
  def veganDishOfTheDay[Mod](implicit d: DishOfTheDayModification.Aux[Vegan, Mod]): Mod = ???

  // DOES compile
  veganDishOfTheDay: CheeselessPizza
}

但我们不希望这样,因为它允许我们生成不是当天菜肴的子类型的菜肴。

有谁知道为什么Aux 模式中的继承会导致失败,或者我可以如何使用中间隐式构造程序来尝试解决问题?

【问题讨论】:

    标签: scala types type-level-computation


    【解决方案1】:

    尝试用证据替换泛型上的绑定:

    trait Pizza
    trait CheeselessPizza extends Pizza
    
    sealed trait DietaryRequirement
    trait Vegan extends DietaryRequirement
    
    sealed trait DishOfTheDay[Meal]
    
    object DishOfTheDay {
      implicit val dishOfTheDay: DishOfTheDay[Pizza] = null
    }
    
    sealed trait ModifiedMeal[Meal, D <: DietaryRequirement] { type Mod <: Meal }
    
    object ModifiedMeal {
      type Aux[Meal, D <: DietaryRequirement, Mod0 /*<: Meal*/] = ModifiedMeal[Meal, D] { type Mod = Mod0 }
    
      //implicit val veganPizzaModifiedMeal: ModifiedMeal.Aux[Pizza, Vegan, CheeselessPizza] = null
    
      def mkAux[Meal, D <: DietaryRequirement, Mod](implicit ev: Mod <:< Meal): Aux[Meal, D, Mod] = null
    
      implicit val veganPizzaModifiedMeal: ModifiedMeal.Aux[Pizza, Vegan, CheeselessPizza] = mkAux
    }
    
    trait DishOfTheDayModification[Req <: DietaryRequirement] { type Out }
    
    object DishOfTheDayModification {
      type Aux[Req <: DietaryRequirement, Out0] = DishOfTheDayModification[Req] { type Out = Out0 }
    
      implicit def dishOfTheDayModification[Meal, Req <: DietaryRequirement, Mod /*<: Meal*/](implicit 
        d: DishOfTheDay[Meal],
        impl: ModifiedMeal.Aux[Meal, Req, Mod],
        ev: Mod <:< Meal
      ): DishOfTheDayModification.Aux[Req, Mod] = null
    }
    
    object MealTesting {
      def veganDishOfTheDay[Mod](implicit d: DishOfTheDayModification.Aux[Vegan, Mod]): Mod = ???
    
      veganDishOfTheDay: CheeselessPizza
    }
    

    【讨论】:

    • 谢谢,这确实解决了这个问题。但是,继承对于与我在这里没有复制的其他位的集成很重要。例如作用于 Pizza 的类型类。你不能在这里使用它,因为我们丢失了关于 Mod &lt;: Meal 的信息。您是否知道为什么这种继承会破坏一切 - 或者如何欺骗编译器重新推断基于 &lt;:&lt; 类型类的继承?我在画一个空白
    • 我稍微更新了我的答案。我将implicit val veganPizzaModifiedMeal: ModifiedMeal.Aux[Pizza, Vegan, CheeselessPizza] = null 替换为def mkAux[Meal, D &lt;: DietaryRequirement, Mod](implicit ev: Mod &lt;:&lt; Meal): Aux[Meal, D, Mod] = null implicit val veganPizzaModifiedMeal: ModifiedMeal.Aux[Pizza, Vegan, CheeselessPizza] = mkAux
    • 我不确定我是否理解您为什么认为我们会泄露有关 Mod &lt;: Meal 的信息。我想我们没有,我们只是通过隐式解析而不是通过类型推断来检查它。
    • 不幸的是,如果有不止一种膳食修改(即 PotRoast 和 NutRoast 作为纯素版本,继续使用该模式),则交换隐式顺序不起作用。当天的菜必须先修复Meal。我刚刚发现原始代码在 scala 2.13.0-M3 中编译(虽然不是 M4 或 M5)所以我会在 M3 中闲逛一段时间:)
    【解决方案2】:

    您的原始方法非常接近没有问题,只需对 1 dishOfTheDayModification 签名进行细微调整,即可使用 Scala v2.12 成功编译。

    作为参考,在原始的DishOfTheDayModification 对象定义中是这样的:

    // Find the dish of the day, then find a ModifiedMeal of it
    // <: Meal is important here so we pick up ONLY pizzas and not some other meal
    implicit def dishOfTheDayModification[Meal, Req <: DietaryRequirement, Mod <: Meal](
      implicit
         // vvvvvv - Here's the problem
         d: DishOfTheDay[Meal],
         impl: ModifiedMeal.Aux[Meal, Req, Mod]
      ): DishOfTheDayModification.Aux[Req, Mod] = null
    

    将顺序改为:

    implicit def dishOfTheDayModification[Meal, Req <: DietaryRequirement, Mod <: Meal](
      implicit
         impl: ModifiedMeal.Aux[Meal, Req, Mod],
         d: DishOfTheDay[Meal]
    ): DishOfTheDayModification.Aux[Req, Mod] = null
    

    允许编译器在解析d之前为impl成功统一MealMod

    【讨论】:

    • 不幸的是,如果有多个 ModifiedMeal.Aux 实例,这将失败。即,另一个类型为ModifiedMeal.Aux[PotRoast, Vegan, NutRoast] 的实例导致失败
    • 假设在您的示例中使用相同的通用类型定义,在object ModifiedMeal 中定义implicit val veganPizzaModifiedMealimplicit val veganPotRoastModifiedMeal 会导致编译器在解析DishOfTheDayModification.Aux[Req, Mod] 时可以使用多个令人满意的隐式。如果可能,我建议使用类型类方法来解决歧义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-23
    • 2021-02-03
    • 2014-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多