具有继承类型的Aux模式无法推理

时间:2018-10-05 07:45:41

标签: scala types type-level-computation

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

我的代码有一个问题,如果我们根据另一个通用类型对Aux-pattern-pattern生成的类型表示类型约束,则会导致类型推断失败。

这些是饭菜,实际上,会有很多比萨饼和许多基本饭菜:

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类型的类,它接受膳食和饮食要求,并生成满足要求的次餐。子类型在这里很重要:

// <: 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
}

问题是调用此方法不能编译,但是应该

如果您复制整个程序,但从生成的膳食中删除了<: 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模式中的继承会导致失败,或者我如何用中间隐式结构构造程序来尝试解决问题?

2 个答案:

答案 0 :(得分:2)

尝试用证据代替泛型的约束:

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
}

答案 1 :(得分:0)

您原来的方法几乎没有问题,只需要对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

在解决Meal之前,允许编译器为Mod成功地将impld统一。