单例对象的抽象类型成员 [英] Abstract type member of a singleton object

查看:59
本文介绍了单例对象的抽象类型成员的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

抽象成员方法在单例对象中是非法的

scala>对象 Foo {|def g: 整数|}def g: 整数^第 2 行:错误:只有特征和抽象类可以声明但未定义的成员

作为抽象值成员

scala>对象 Foo {|val x: 整数|}val x: 整数^第 2 行:错误:只有特征和抽象类可以声明但未定义的成员

但是抽象类型成员在单例对象中是合法的

scala>对象 Foo {|A型|}对象 Foo

很明显,类型成员是抽象的意义不同于其他抽象成员.有什么区别?抽象类型成员如何能有用,因为对象是最终的,它似乎无法具体化?

解决方案

嗯,事情是 type 不必是具体的.你可以写:

类型任意

在 Ammonite 中编译并运行.您甚至可以将其用作参数!

类型任意def foo(a: Arbitrary): List[Arbitrary] = List(a)

唯一的问题是,编译器对 Arbitrary 一无所知(例如它<: String 或其他东西)这将允许您合法地创建一个这种类型的值.

从这个角度来看,抽象类型成员只是一种我们一无所知的类型,但我们可以只知道它存在并且值属于这种类型.

但是,我们也可以覆盖这个空定义,使其更具体,例如

type Arbitrary = String任意类型<:AnyVal输入任意 >: 用户

那么任何实现它的人都可以访问完整的类型信息,而编写在范围内具有抽象定义的代码的程序员只能传递类型.

偶然地,这就是我们重新发现路径依赖类型的方式,或者更确切地说,如果我们想要拥有路径依赖类型而无需手动维护我们不想要的情况列表,那么能够创建抽象类型是必要的

在 Scala 2 中的 Cats 中有另一个用例.Edward Kmett 显然发现了一种使用抽象类型的模式member 和 .asInstanceOf 来解决缺少我们可以提升到类型类的多态函数:

<预><代码>特性 DoubleSize[F[_]] {def double[A](fa: F[A]): F[A]}对象双尺寸{类型任意def instance[F[_]](fun: F[Arbitrary] => F[Arbitrary]): DoubleSize[F] = new DoubleSize[F] {def double[A](fa: F[A]): F[A] = fun(fa.asInstanceOf[F[Arbitrary]]).asInstanceOf[F[A]]}//在 Dotty 中我们可以做//def instance[F[_]](fun: [A] => F[A] => F[A]) = new DoubleSize[F] {//def double[A](fa: F[A]): F[A] = fun[A](fa)//}//但在 Scala 2 中这是不可能的}val doubleSize = DoubleSize.instance[List] { list =>列表++列表}doubleSize.double(List(1,2,3))doubleSize.double(List("a", "b", "c"))

如果我们排除抽象类型成员,这种变通方法将是不可能的,尽管在 Dotty 中,这种特殊的变通方法不再是必要的.

Abstract member method is illegal in a singleton object

scala> object Foo {
     |   def g: Int
     | }
         def g: Int
             ^
On line 2: error: only traits and abstract classes can have declared but undefined members

as is abstract value member

scala> object Foo {
     |   val x: Int
     | }
         val x: Int
             ^
On line 2: error: only traits and abstract classes can have declared but undefined members

however abstract type member is legal in a singleton object

scala> object Foo {
     |   type A
     | }
object Foo

so clearly the sense in which a type member is abstract is different from other abstract members. What is the difference? How can an abstract type member be useful when it seems it cannot be made concrete since object is final?

解决方案

Well, the things is type doesn't have to be concrete. You can write:

type Arbitrary

in Ammonite and it compiles and runs. You can even use it as argument!

type Arbitrary

def foo(a: Arbitrary): List[Arbitrary] = List(a)

The only issue is, that compiler doesn't know anything about Arbitrary (e.g. that it <: String or something) which would allow you to legally create a value of this type.

From that point of view, abstract type member is just a type we don't know anything about, but which we can around knowing only that it exist and value would be of this type.

But, we can also override this empty definition by making it more specific e.g.

type Arbitrary = String
type Arbitrary <: AnyVal
type Arbitrary >: User

Then whoever implements it will have access to full type information, while programmer writing code that has abstract definition in scope can only pass type around.

Accidentally, that's how we rediscovered path-dependent types, or rather figured out that being able to create abstract types is kind of necessary if we want to have path-dependent types without manually maintained list of cases where we don't want them.

In Cats in Scala 2 this has another use case. There is a pattern which apparently was discovered by Edward Kmett that uses abstract type member and .asInstanceOf to workaround missing polymorphic functions that we could lift to a type class:


trait DoubleSize[F[_]] {
  def double[A](fa: F[A]): F[A]
}
object DoubleSize {

  type Arbitrary
  def instance[F[_]](fun: F[Arbitrary] => F[Arbitrary]): DoubleSize[F] = new DoubleSize[F] {
    def double[A](fa: F[A]): F[A] = fun(fa.asInstanceOf[F[Arbitrary]]).asInstanceOf[F[A]]
  }

  // in Dotty we could do
  // def instance[F[_]](fun: [A] => F[A] => F[A]) = new DoubleSize[F] {
  //    def double[A](fa: F[A]): F[A] = fun[A](fa)
  // }
  // but in Scala 2 it's impossible
}

val doubleSize = DoubleSize.instance[List] { list =>
  list ++ list
}

doubleSize.double(List(1,2,3))
doubleSize.double(List("a", "b", "c"))

This kind of workarounds wouldn't be possible if we ruled out abstract type members, though in Dotty this particular workaround won't be necessary anymore.

这篇关于单例对象的抽象类型成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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