具有继承类型的辅助模式推理失败



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

我的代码存在一个问题,如果我们基于另一个泛型类型对Aux模式生成的类型表示类型约束,则类型推断失败。

这些是膳食,实际上会有很多种类的比萨饼和许多基础餐:

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
}

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

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

// <: 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模式中的继承会导致失败,或者我如何使用中间隐式来构建程序以尝试解决问题?

尝试用证据替换泛型上的界限:

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
}

您最初的方法非常接近没有问题,只需要对 1dishOfTheDayModification签名进行微小调整即可使用 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之前统一Meal并成功Modimpl

最新更新