使用“否定地"使用上下文范围的方法包括:确保类型类实例不在范围内 [英] Using context bounds "negatively" to ensure type class instance is absent from scope

查看:75
本文介绍了使用“否定地"使用上下文范围的方法包括:确保类型类实例不在范围内的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tl; dr :我该如何执行以下组成的代码:

tl;dr: How do I do something like the made up code below:

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

"Not[Functor]"是这里的组成部分.
当提供的'm'不是函子时,我希望它成功,否则使编译器失败.

The 'Not[Functor]', being the made up part here.
I want it to succeed when the 'm' provided is not a Functor, and fail the compiler otherwise.

已解决:跳过其余的问题,然后直接转到下面的答案.

Solved: skip the rest of the question and go right ahead to the answer below.

粗略地说,我要完成的工作是负面证据".

What I'm trying to accomplish is, roughly speaking, "negative evidence".

伪代码如下所示:

// 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()在相关位置比较可取,因为它可以节省遍历集合的遍历.

Keep in mind that fixedSizeOf() is preferable where relevant, since it saves us the traversal over the collection.

这样,对于仅定义了Length的容器类型(但没有定义Foldable的容器),对于定义了FixedSizeOf的元素,我们可以提高性能.

This way, for container types where only Length is defined (but not Foldable), and for elements where a FixedSizeOf is defined, we get improved performance.

在其余情况下,我们遍历集合并求和各个大小.

For the rest of the cases, we go over the collection and sum individual sizes.

我的问题是在为容器定义了LengthFoldable的同时为元素定义了FixedSizeOf的情况.这是一种非常常见的情况(例如:List[Int]都已定义).

My problem is in the cases where both Length and Foldable are defined for the container, and FixedSizeOf is defined for the elements. This is a very common case here (e.g.,: List[Int] has both defined).

示例:

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类型类.

What I would like is to be able to rely on the Foldable type class only when the Length+FixedSizeOf combination does not apply.

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

For that purpose, I can change the definition of foldSizeOf() to accept VarSizeOf elements:

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

现在我们必须填写有问题的部分,该部分涵盖了Foldable容器,其中包含FixedSizeOf元素和未定义Length .我不确定该如何处理,但是伪代码看起来像这样:

And now we have to fill in the problematic part that covers Foldable containers with FixedSizeOf elements and no Length defined. I'm not sure how to approach this, but pseudo-code would look something like:

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

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

The 'Not[Length]', obviously, being the made up part here.

我知道的部分解决方案

1)为低优先级隐式定义一个类,并对其进行扩展,如'object Predef extends LowPriorityImplicits'所示. 最后一个隐式(foldSizeOfFixed())可以在父类中定义,并将被后代类替代.

1) Define a class for low priority implicits and extend it, as seen in 'object Predef extends LowPriorityImplicits'. The last implicit (foldSizeOfFixed()) can be defined in the parent class, and will be overridden by alternative from the descendant class.

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

I am not interested in this option because I'd like to eventually be able to support recursive usage of SizeOf, and this will prevent the implicit in the low priority base class from relying on those in the sub class (is my understanding here correct? wrong! implicit lookup works from the context of the sub class, this is a viable solution!)

2)一种更粗略的方法是依赖Option[TypeClass](例如:Option[Length[List]].我可以写一些其中的一个大ol'隐式方法,将FoldableSizeOf选作强制性的,而将FixedSizeOf为可选,如果可用,则依赖后者.(来源:此处)

2) A rougher approach is relying on Option[TypeClass] (e.g.,: Option[Length[List]]. A few of those and I can just write one big ol' implicit that picks Foldable and SizeOf as mandatory and Length and FixedSizeOf as optional, and relies on the latter if they are available. (source: here)

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

The two problems here are lack of modularity and falling back to runtime exceptions when no relevant type class instances can be located (this example can probably be made to work with this solution, but that's not always possible)

这是我使用可选隐式函数所能获得的最好的结果.还不存在:

This is the best I was able to get with optional implicits. It's not there yet:

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()发生冲突外,这还是有必要的.

Except this collides with fixedSizeOf() from earlier, which is still necessary.

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

Thanks for any help or perspective :-)

推荐答案

我最终使用基于歧义的解决方案解决了这一问题,该解决方案不需要使用继承进行优先级排序.

I eventually solved this using an ambiguity-based solution that doesn't require prioritizing using inheritance.

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

Here is my attempt at generalizing this.

我们使用类型Not[A]构造否定类型类:

We use the type Not[A] to construct negative type classes:

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

...然后可以用作上下文范围:

...which can then be used as context bounds:

def foo[T: NotMonoid] = ...

我们首先要确保Not [A]的每个有效表达式都将至少获取一个隐式实例.

We proceed by ensuring that every valid expression of Not[A] will gain at least one implicit instance.

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

该实例被称为'notA'-'not',因为如果它是唯一一个针对'Not [TC [A]]'的实例,那么将发现否定类型类适用;对于处理扁平形类型(例如Int)的方法,通常会附加"A".

The instance is called 'notA' -- 'not' because if it is the only instance found for 'Not[TC[A]]' then the negative type class is found to apply; the 'A' is commonly appended for methods that deal with flat-shaped types (e.g. Int).

我们现在引入一种歧义,以消除应用了不希望的类型类 的情况:

We now introduce an ambiguity to turn away cases where the undesired type class is applied:

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

这几乎与'NotA'完全相同,除了在这里我们只对隐式作用域中存在由'TC'指定的类型类实例的类型感兴趣.该实例名为"notNotA",因为仅匹配查找的隐式实例,它将与"notA"产生歧义,从而使隐式搜索失败(这是我们的目标).

This is almost exactly the same as 'NotA', except here we are only interested in types for which an instance of the type class specified by 'TC' exists in implicit scope. The instance is named 'notNotA', since by merely matching the implicit being looked up, it will create an ambiguity with 'notA', failing the implicit search (which is our goal).

我们来看一个用法示例.我们将从上面使用'NotMonoid'否定类型类:

Let's go over a usage example. We'll use the 'NotMonoid' negative type class from above:

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 [_ [_]]的类型类.我们也为它们添加隐式:

So far so good! However, types shaped M[_] and type classes shaped TC[_[_]] aren't supported yet by the scheme above. Let's add implicits for them as well:

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对样板有一个变通办法,因为它可以处理几种类型的形状-查找未应用".我无法在基本情况下使用它(形状TC [_]的类型类,例如Monoid),即使它像魅力一样在TC [_ [_]](例如Functor)上工作,所以这个答案不能解决这个问题.

Simple enough. Note that Scalaz has a workaround for the boilerplate resulting from dealing with several type shapes -- look for 'Unapply'. I haven't been able to make use of it for the basic case (type class of shape TC[_], such as Monoid), even though it worked on TC[_[_]] (e.g. Functor) like a charm, so this answer doesn't cover that.

如果有人感兴趣,这里是一个摘要中的所有内容:

If anybody's interested, here's everything needed in a single snippet:

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

和我在问题中要求的伪代码看起来像这样(实际代码):

and the pseudo code I asked for in the question would look like so (actual code):

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

更新:类似的技术适用于隐式转换:

Update: Similar technique applied to implicit conversions:

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]] {}
}

我们现在可以(例如)定义一个函数,该函数仅在无法按订购"查看其值的类型时才接受它们:

We can now (e.g.) define a function that will only admit values if their types aren't viewable as Ordered:

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

这篇关于使用“否定地"使用上下文范围的方法包括:确保类型类实例不在范围内的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆