使用上下文“负向”边界来确保类型类实例不存在于范围内

2024-01-16

tl;dr:我该如何做类似下面的代码:

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

The 'Not[Functor]',这是这里的组成部分。
我希望它在提供的“m”不是函子时成功,否则编译器将失败。

Solved:跳过问题的其余部分,直接进入下面的答案。


粗略地说,我想要完成的是“反面证据”。

伪代码看起来像这样:

// 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定义后,我们得到了改进的性能。

对于其余的情况,我们会检查集合并汇总各个尺寸。

我的问题是在两种情况下Length and Foldable为容器定义,并且FixedSizeOf是为元素定义的。这是这里很常见的情况(例如:List[Int]两者均已定义)。

Example:

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))

我想要的是能够依靠Foldable仅当类型类Length+FixedSizeOf组合不适用。

为此,我可以更改的定义foldSizeOf()接受VarSizeOf要素:

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

现在我们必须填写有问题的部分Foldable容器与FixedSizeOf元素和no Length defined。我不确定如何解决这个问题,但伪代码看起来像:

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

The 'Not[Length]',显然,这是这里的组成部分。

我所知道的部分解决方案

1)为低优先级隐式定义一个类并扩展它,如'object Predef extends LowPriorityImplicits'。 最后一个隐式(foldSizeOfFixed()) 可以在父类中定义,并将被子孙类中的替代项覆盖。

我对此选项不感兴趣,因为我希望最终能够支持递归使用SizeOf,这将防止低优先级基类中的隐式依赖子类中的隐式(我的理解正确吗?编辑:错误!隐式查找在子类的上下文中工作,这是一个可行的解决方案!)

2)更粗略的方法是依赖Option[TypeClass](例如。,:Option[Length[List]]。其中一些,我可以写一个大的隐式选择Foldable and SizeOf作为强制性和Length and FixedSizeOf作为可选,并且依赖后者(如果可用)。 (来源:here http://www.scala-lang.org/node/8773)

这里的两个问题是缺乏模块化以及当无法找到相关类型类实例时回退到运行时异常(这个示例可能可以与此解决方案一起使用,但这并不总是可能的)

编辑:这是我能够通过可选隐式获得的最好结果。还没有到:

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()从早些时候开始,这仍然是必要的。

感谢您的任何帮助或观点:-)


我最终使用基于歧义的解决方案解决了这个问题,该解决方案不需要使用继承来确定优先级。

这是我对此进行概括的尝试。

我们使用类型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]]”找到的唯一实例,则发现适用负类型类; “A”通常附加在处理扁平类型(例如 Int)的方法上。

我们现在引入歧义来拒绝不需要的类型类的情况is应用:

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

这几乎与“NotA”完全相同,只不过这里我们只对“TC”指定的类型类实例存在于隐式作用域中的类型感兴趣。该实例被命名为“notNotA”,因为仅通过匹配正在查找的隐式搜索,它将与“notA”产生歧义,从而使隐式搜索失败(这是我们的目标)。

让我们看一个用法示例。我们将使用上面的“NotMonoid”负类型类:

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[_] 的类型类,例如 Monoid),尽管它像魅力一样在 TC[_[_]](例如 Functor)上工作,所以这个答案不包括这一点。

如果有人感兴趣,以下是一个片段中所需的所有内容:

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"

Update:类似的技术应用于隐式转换:

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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用上下文“负向”边界来确保类型类实例不存在于范围内 的相关文章

随机推荐