将Shapeless HList转换为元组

5
我有一个在Scala中使用的try-with-resources版本。我想知道是否可以使用Shapeless和HList制作一个通用版本?
import scala.util.{Failure, Success, Try}

class Loan1[A <: AutoCloseable](resource: A) {
  def to[B](block: A => B): B = {
    Try(block(resource)) match {
      case Success(result) =>
        resource.close()
        result
      case Failure(e) =>
        resource.close()
        throw e
    }
  }
}

class Loan2[A <: AutoCloseable, B <: AutoCloseable](r1: A, r2: B){
  def to[R](block: (A,B) => R): R = {
    Try(block(r1,r2)) match {
      case Success(result) =>
        r1.close(); r2.close()
        result
      case Failure(e) =>
        r1.close(); r2.close()
        throw e
    }
  }
}

object Loan {

  def apply[A <: AutoCloseable](resource: A): Loan1[A] = new Loan1(resource)

  def apply[A <: AutoCloseable, B <: AutoCloseable] (r1: A, r2: B)= new Loan2(r1, r2)

}

我猜这里的签名类似

  def apply[L <: HList](list: L)(implicit con: LUBConstraint[L, AutoCloseable]) = ???

还有一个问题是如何以元组的形式在block: (A,B) => R部分中使用元素?

这个能实现吗?


我会从一个typeclass开始,而不是使用上界——这样可以在实现方面给您更多的自由。 - jdevelop
1个回答

4
其实并不难。你需要一种方法从元组中获取一个 HList (Generic.Aux[Tup, L]),以及一种方法从 Hlist 获取一个List[AutoClosable] (ToList[L, AutoCloseable]).
虽然可能有其他的方法可以实现这一点而不需要使用 ToList,但使用它就能很容易地将LUBConstraint[L, AutoCloseable]和能够在每个资源上调用 close()的需求结合在一起。
scala> :paste
// Entering paste mode (ctrl-D to finish)

import shapeless._, ops.hlist._
import scala.util.{Failure, Success, Try}

class Loan[Tup, L <: HList](resources: Tup)(
  implicit 
  gen: Generic.Aux[Tup, L],
  con: ToList[L, AutoCloseable]
) {
  def to[B](block: Tup => B): B = {
    Try(block(resources)) match {
      case Success(result) =>
        gen.to(resources).toList.foreach { _.close() }
        result
      case Failure(e) =>
        gen.to(resources).toList.foreach { _.close() }
        throw e
    }
  }
}

object Loan {
    def apply[Tup, L <: HList](resources: Tup)(
      implicit 
      gen: Generic.Aux[Tup, L],
      con: ToList[L, AutoCloseable]
    ) = new Loan(resources)
}

// Exiting paste mode, now interpreting.


scala> class Bar() extends AutoCloseable { def close = println("close Bar"); def IAmBar = println("doing bar stuff") }
defined class Bar

scala> class Foo() extends AutoCloseable { def close = println("close Foo"); def IAmFoo = println("doing foo stuff") }
defined class Foo

scala> Loan(new Foo, new Bar).to{ case (f, b) => f.IAmFoo; b.IAmBar }
doing foo stuff
doing bar stuff
close Foo
close Bar

唯一的问题在于,当资源数目恰好为1时,您需要编写Tuple1(new Foo)并像case Tuple1(f)一样进行模式匹配。最简单的解决方案是保留Loan1部分,并使用shapeless实现LoanN来替换Loan2部分,以便适用于所有大于1的度数。这几乎等同于将我的解决方案复制粘贴到您的解决方案中,并将我的Loan类重命名为LoanN
import shapeless._, ops.hlist._, ops.nat._
import scala.util.{Failure, Success, Try}

class LoanN[Tup, L <: HList](resources: Tup)(
  implicit 
  gen: Generic.Aux[Tup, L],
  con: ToList[L, AutoCloseable]
) {
  def to[B](block: Tup => B): B = {
    Try(block(resources)) match {
      case Success(result) =>
        gen.to(resources).toList.foreach { _.close() }
        result
      case Failure(e) =>
        gen.to(resources).toList.foreach { _.close() }
        throw e
    }
  }
}

class Loan1[A <: AutoCloseable](resource: A) {
  def to[B](block: A => B): B = {
    Try(block(resource)) match {
      case Success(result) =>
        resource.close()
        result
      case Failure(e) =>
        resource.close()
        throw e
    }
  }
}


object Loan {
    def apply[A <: AutoCloseable](resource: A): Loan1[A] = new Loan1(resource)
    def apply[Tup, L <: HList, Len <: Nat](resources: Tup)(
      implicit 
      gen: Generic.Aux[Tup, L],
      con: ToList[L, AutoCloseable],
      length: Length.Aux[L, Len],
      gt: GT[Len, nat._1]
    ) = new LoanN(resources)
}

我还添加了一条约束条件,输入的长度必须大于1。否则将会有一个漏洞,您可以传入一个case class Baz(),它可以转换为List[Nothing],这是List[AutoClosable]的子类型。
毫无疑问,使用Loan1的额外样板代码仍然可以通过编写更复杂的类型类自己来消除,该类型类能够区别单个参数和参数元组。
您提议接受HList作为参数并将其转换为元组。这也是可能的,使用shapeless.ops.hlist.Tupler即可实现。然后,当然,该API的用户将不得不自己构造HList,而且您仍然面临Scala没有漂亮的语法来取消包装Tuple1的问题。第二个问题可以通过一个非常简单的自定义类型类来解决,该类型类将Tuple1[A]解包为A并保持其余内容不变:
sealed trait Unwrap[In] { 
  type Out
  def apply(in: In): Out 
}

object Unwrap extends DefaultUnwrap {
  type Aux[In, Out0] = Unwrap[In] { type Out = Out0 }
  def apply[T](implicit unwrap: Unwrap[T]): Unwrap.Aux[T, unwrap.Out] = unwrap

  implicit def unwrapTuple1[A]: Unwrap.Aux[Tuple1[A], A] = new Unwrap[Tuple1[A]] {
    type Out = A
    def apply(in: Tuple1[A]) = in._1
  }
}
trait DefaultUnwrap {
  implicit def dontUnwrapOthers[A]: Unwrap.Aux[A, A] = new Unwrap[A] {
    type Out = A
    def apply(in: A) = in
  }
}

Tupler与此相结合,您就会得到一个相对简单的解决方案:
scala> :paste
// Entering paste mode (ctrl-D to finish)

import shapeless._, ops.hlist._
import scala.util.{Failure, Success, Try}

class LoanN[Tup, L <: HList, Res](resources: L)(
  implicit 
  tupler: Tupler.Aux[L, Tup],
  con: ToList[L, AutoCloseable],
  unwrap: Unwrap.Aux[Tup, Res]
) {
  def to[B](block: Res => B): B = {
    Try(block(unwrap(tupler(resources)))) match {
      case Success(result) =>
        resources.toList.foreach { _.close() }
        result
      case Failure(e) =>
        resources.toList.foreach { _.close() }
        throw e
    }
  }
}


object Loan {
    def apply[Tup, L <: HList, Res](resources: L)(
      implicit 
      tupler: Tupler.Aux[L, Tup],
      con: ToList[L, AutoCloseable],
      unwrap: Unwrap.Aux[Tup, Res]
    ) = new LoanN(resources)
}

// Exiting paste mode, now interpreting.


scala> Loan(new Foo :: new Bar ::  HNil).to{ case (f,b) => f.IAmFoo; b.IAmBar }
doing foo stuff
doing bar stuff
close Foo
close Bar

scala> Loan(new Foo :: HNil).to{ case (f) => f.IAmFoo }
doing foo stuff
close Foo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接