scala 使用“负面”上下文界限以确保类型类示例不在作用域中

w6mmgewl  于 2022-11-09  发布在  Scala
关注(0)|答案(2)|浏览(152)

tl;dr:我应该怎么做,比如下面这些虚构的代码:

def notFunctor[M[_] : Not[Functor]](m: M[_]) = s"$m is not a functor"

Not[Functor]”,这里是虚构的部分。
当提供的‘m’不是函数器时,我希望它成功,否则使编译器失败。

已解决:跳过剩下的问题,直接进入下面的答案。

粗略地说,我想要做的是“负面证据”。
伪代码如下所示:

// type class for obtaining serialization size in bytes.
trait SizeOf[A] { def sizeOf(a: A): Long }

// type class specialized for types whose size may vary between instances
trait VarSizeOf[A] extends SizeOf[A]

// type class specialized for types whose elements share the same size (e.g. Int)
trait FixedSizeOf[A] extends SizeOf[A] {
  def fixedSize: Long
  def sizeOf(a: A) = fixedSize
}

// SizeOf for container with fixed-sized elements and Length (using scalaz.Length)
implicit def fixedSizeOf[T[_] : Length, A : FixedSizeOf] = new VarSizeOf[T[A]] {
  def sizeOf(as: T[A]) = ... // length(as) * sizeOf[A]
}

// SizeOf for container with scalaz.Foldable, and elements with VarSizeOf
implicit def foldSizeOf[T[_] : Foldable, A : SizeOf] = new VarSizeOf[T[A]] {
  def sizeOf(as: T[A]) = ... // foldMap(a => sizeOf(a))
}

请记住,在相关的情况下,fixedSizeOf()更可取,因为它为我们节省了遍历集合的时间。
这样,对于只定义了Length(但没有定义Foldable)的容器类型,以及定义了FixedSizeOf的元素,我们可以提高性能。
对于其余的案例,我们将检查收集的数据并计算各个尺寸的总和。
我的问题是同时为容器定义了LengthFoldable,并为元素定义了FixedSizeOf。这在这里是非常常见的情况(例如:List[Int]两者都已定义)。
示例:

scala> implicitly[SizeOf[List[Int]]].sizeOf(List(1,2,3))
<console>:24: error: ambiguous implicit values:
 both method foldSizeOf of type [T[_], A](implicit evidence$1: scalaz.Foldable[T], implicit evidence$2: SizeOf[A])VarSizeOf[T[A]]
 and method fixedSizeOf of type [T[_], A](implicit evidence$1: scalaz.Length[T], implicit evidence$2: FixedSizeOf[A])VarSizeOf[T[A]]
 match expected type SizeOf[List[Int]]
              implicitly[SizeOf[List[Int]]].sizeOf(List(1,2,3))

我希望的是,只有当Length+FixedSizeOf组合不适用时,才能依赖Foldable类型类。
为此,我可以更改foldSizeOf()的定义以接受VarSizeOf元素:

implicit def foldSizeOfVar[T[_] : Foldable, A : VarSizeOf] = // ...

现在我们必须填充包含FixedSizeOf元素和*未定义Length*的Foldable容器的有问题的部分。我不确定如何实现这一点,但伪代码应该类似于:

implicit def foldSizeOfFixed[T[_] : Foldable : Not[Length], A : FixedSizeOf] = // ...

显然,‘Not[Length]’是这里虚构的部分。

我知道的部分解决方案

1)为低优先级定义一个类,隐含并扩展它,如‘object Predef extends LowPriorityImplicits’所示。最后一个隐式(foldSizeOfFixed())可以在父类中定义,并将被子类中的替换项覆盖。
我对这个选项不感兴趣,因为我希望最终能够支持SizeOf的递归使用,这将防止低优先级基类中的隐式依赖于子类中的那些(我的理解正确吗?编辑:错误!隐式查找从子类的上下文中工作,这是一个可行的解决方案!)
2)更粗糙的方法是依赖Option[TypeClass](例如:Option[Length[List]])。其中的几个,我可以只写一个大的ol‘隐式,选择FoldableSizeOf作为必选的,选择LengthFixedSizeOf作为可选的,并依赖于后者(如果它们可用)。(来源:here)
这里的两个问题是缺乏模块化和在找不到相关的类型类示例时回退到运行时异常(此示例可能可以与此解决方案一起使用,但这并不总是可能的)
编辑:这是我所能得到的最好的可选暗示。它还不在那里:

implicit def optionalTypeClass[TC](implicit tc: TC = null) = Option(tc)
type OptionalLength[T[_]] = Option[Length[T]]
type OptionalFixedSizeOf[T[_]] = Option[FixedSizeOf[T]]

implicit def sizeOfContainer[
    T[_] : Foldable : OptionalLength,
    A : SizeOf : OptionalFixedSizeOf]: SizeOf[T[A]] = new SizeOf[T[A]] {
  def sizeOf(as: T[A]) = {

    // optionally calculate using Length + FixedSizeOf is possible
    val fixedLength = for {
      lengthOf <- implicitly[OptionalLength[T]]
      sizeOf <- implicitly[OptionalFixedSizeOf[A]]
    } yield lengthOf.length(as) * sizeOf.fixedSize

    // otherwise fall back to Foldable
    fixedLength.getOrElse { 
      val foldable = implicitly[Foldable[T]]
      val sizeOf = implicitly[SizeOf[A]]
      foldable.foldMap(as)(a => sizeOf.sizeOf(a))
    }
  }
}

除了这与之前的fixedSizeOf()冲突之外,这仍然是必要的。
感谢您的帮助或观点:-)

nlejzf6q

nlejzf6q1#

我最终使用一种基于歧义的解决方案解决了这个问题,该解决方案不需要使用继承来确定优先级。
以下是我试图概括这一点的尝试。
我们使用类型Not[A]构造负类型类:

import scala.language.higherKinds

trait Not[A]

trait Monoid[_] // or import scalaz._, Scalaz._
type NotMonoid[A] = Not[Monoid[A]] 

trait Functor[_[_]] // or import scalaz._, Scalaz._
type NotFunctor[M[_]] = Not[Functor[M]]

...然后可以用作上下文边界:

def foo[T: NotMonoid] = ...

我们继续确保Not[A]的每个有效表达式都将获得至少一个隐式示例。

implicit def notA[A, TC[_]] = new Not[TC[A]] {}

该示例被称为‘Nota’--‘Not’,因为如果它是为‘Not[TC[A]]’找到的唯一示例,则发现负类型类适用;对于处理平面类型的方法(例如Int),通常附加‘A’。
我们现在引入一个二义性,以避免应用不需要的类型类*的情况:

implicit def notNotA[A : TC, TC[_]] = new Not[TC[A]] {}

这与‘nota’几乎完全相同,只是这里我们只对‘tc’指定的类型类的示例存在于隐式作用域中的类型感兴趣。该示例被命名为‘noNotA’,因为仅仅通过匹配被查找的隐式搜索,它将创建与‘nota’的歧义,从而导致隐式搜索失败(这是我们的目标)。
让我们复习一下一个用法示例。我们将使用上面的‘NotMonid’负类型类:

implicitly[NotMonoid[java.io.File]] // succeeds
implicitly[NotMonoid[Int]] // fails

def showIfNotMonoid[A: NotMonoid](a: A) = a.toString

showIfNotMonoid(3) // fails, good!
showIfNotMonoid(scala.Console) // succeeds for anything that isn't a Monoid

到目前一切尚好!然而,上面的方案还不支持形状为M[_]的类型和形状为TC[__]的类型类。让我们也为他们添加暗示:

implicit def notM[M[_], TC[_[_]]] = new Not[TC[M]] {}
implicit def notNotM[M[_] : TC, TC[_[_]]] = new Not[TC[M]] {}

implicitly[NotFunctor[List]] // fails
implicitly[NotFunctor[Class]] // succeeds

很简单。注意,Scalaz有一个解决方案,可以解决处理几个文字形状所产生的样板问题--查找“UnApply”。我还不能在基本情况下使用它(形状为TC[_]的类型类,如Monid),即使它对TC_[_]非常有效,所以这个答案不包括这一点。
如果有人感兴趣,以下是一小段视频中所需的一切:

import scala.language.higherKinds

trait Not[A]

object Not {
  implicit def notA[A, TC[_]] = new Not[TC[A]] {}
  implicit def notNotA[A : TC, TC[_]] = new Not[TC[A]] {}

  implicit def notM[M[_], TC[_[_]]] = new Not[TC[M]] {}
  implicit def notNotM[M[_] : TC, TC[_[_]]] = new Not[TC[M]] {}
}

import Not._

type NotNumeric[A] = Not[Numeric[A]]
implicitly[NotNumeric[String]] // succeeds
implicitly[NotNumeric[Int]] // fails

我在问题中要求的伪代码将如下所示(实际代码):

// NotFunctor[M[_]] declared above
def notFunctor[M[_] : NotFunctor](m: M[_]) = s"$m is not a functor"

**更新:**隐式转换也有类似的技巧:

import scala.language.higherKinds

trait Not[A]

object Not {
  implicit def not[V[_], A](a: A) = new Not[V[A]] {}
  implicit def notNot[V[_], A <% V[A]](a: A) = new Not[V[A]] {}
}

我们现在可以(例如)定义一个函数,该函数仅在值的类型不能按顺序查看时才接受值:

def unordered[A <% Not[Ordered[A]]](a: A) = a
2admgd59

2admgd592#

在Scala3(又名Dotty)中,上述技巧不再起作用。给定值的否定是NotGiven内置的:

def f[T](value: T)(using ev: NotGiven[MyTypeclass[T]])

例如:

f("ok") // no given instance of MyTypeclass[T] in scope
given MyTypeclass[String] = ... // provide the typeclass
f("bad") // compile error

相关问题