【问题标题】:Eliminating identity wrapper types from Scala APIs从 Scala API 中消除身份包装器类型
【发布时间】:2019-06-15 11:03:36
【问题描述】:

假设我正在尝试“抽象执行”:

import scala.language.higherKinds

class Operator[W[_]]( f : Int => W[Int] ) {
  def operate( i : Int ) : W[Int] = f(i)
}

现在我可以定义Operator[Future]Operator[Task] 等。例如...

import scala.concurrent.{ExecutionContext,Future}
def futureSquared( i : Int ) = Future( i * i )( ExecutionContext.global )

REPL 风格...

scala> val fop = new Operator( futureSquared )
fop: Operator[scala.concurrent.Future] = Operator@105c54cb

scala> fop.operate(4)
res0: scala.concurrent.Future[Int] = Future(<not completed>)

scala> res0
res1: scala.concurrent.Future[Int] = Future(Success(16))

万岁!

但我也可能想要一个简单的同步版本,所以我在某个地方定义

type Identity[T] = T

而且我可以定义一个同步操作符...

scala> def square( i : Int ) : Identity[Int] = i * i
square: (i: Int)Identity[Int]

scala> val sop = new Operator( square )
sop: Operator[Identity] = Operator@18f2960b

scala> sop.operate(9)
res2: Identity[Int] = 81

甜。

但是,结果的推断类型是Identity[Int],而不是更简单、直接的Int,这很尴尬。当然,这两种类型实际上是相同的,因此在各方面都是相同的。但我希望我的图书馆的客户不要对这种抽象过度执行的东西一无所知。

我可以手写一个包装器...

class SimpleOperator( inner : Operator[Identity] ) extends Operator[Identity]( inner.operate ) {
  override def operate( i : Int ) : Int = super.operate(i)
}

确实有效...

scala> val simple = new SimpleOperator( sop )
simple: SimpleOperator = SimpleOperator@345c744e

scala> simple.operate(7)
res3: Int = 49

但是这感觉很像样板,特别是如果我的抽象过度执行类有很多方法,而不仅仅是一个。而且我必须记住随着泛型类的发展保持包装器同步。

是否有一些更通用、可维护的方法来获得一个版本的 Operator[Identity] 以使包含类型从类型推断和 API 文档中消失?

【问题讨论】:

    标签: scala


    【解决方案1】:

    这更多的是长评论而不是答案......

    但是,结果的推断类型是 Identity[Int],而不是更简单、直接的 Int,这很尴尬。当然,这两种表观类型实际上是相同的,因此在各方面都是相同的。但我希望我的图书馆的客户不要对这种抽象过度执行的东西一无所知。

    这听起来像是您想将Indentity[T] 转换回T...您考虑过类型归属吗?

    scala>def f[T](t: T): Identity[T] = t
    
    scala>f(3)
    // res11: Identity[Int] = 3
    
    scala>f(3): Int
    // res12: Int = 3
    
    // So in your case
    scala>sop.operate(9): Int
    // res18: Int = 81
    

    【讨论】:

    • 像这样的类型注释可以正常工作!这是一个很好的建议,谢谢。我使用它们在我的示例代码中作弊,因此结果类型看起来很简单。但是他们把负担放在了库的用户身上,让他们明白看起来复杂的类型实际上是简单的类型,这样编译器的这种推动才能起作用。我想知道是否有一些方法可以让天真的用户不必理解所有这些。
    【解决方案2】:

    正如 Steve Waldman 在给定 type Identity[T] = T 的 cmets 中所建议的那样,TIdentity[T] 类型确实是相同的,没有任何仪式,在呼叫站点或其他任何地方都可以替代和透明。例如,以下工作开箱即用

    sop.operate(9) // res2: cats.Id[Int] = 81
    def foo(i: Int) = i
    foo(sop.operate(9)) // res3: Int = 81
    

    来自 Cats 的extractpure 的对偶,并从其上下文中提取值,所以也许我们可以为不熟悉上述等价的用户提供类似的方法(如果你看到我以前的编辑,就像我自己).

    【讨论】:

    • 有了这种身份类型,定义为type Identity[T] = Ttype Id[T] = T,类型TIdentity[T]Id[T]确实是相同的,没有任何仪式,可替代且透明呼叫站点或其他任何地方。对于可能不理解这一点的用户来说,它们只是看起来既可怕又复杂。但最好为它们定义类似extractunwrap 的操作。至少不理解等价性的用户可以轻松地回到他们所理解的内容。感谢您的建议。
    【解决方案3】:

    可以通过显式提供类型来完成,但对于调查方法签名的外部用户来说仍然看起来很神奇。

    type Identity[T] = T
    
    def square( i : Int ):Int = i * i
    
    class Operator[W[_], T <: W[Int] ]( f : Int => T ) {
      def operate(i : Int):T = f(i)
    }
    
    val op = new Operator[Identity,Int](square)
    
    op.operate(5)
    //res0: Int = 25
    

    也适用于new Operator[Future,Future[Int]]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多