2017-02-14 12 views
3

私はtry-with-resourcesのこのバージョンをScalaに持っています。 ShapelessとHListを使ってこれをジェネリック版にすることは可能でしょうか?シェイプレス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セクション内のタプルの形での要素が利用できるようにする方法ですか?

これは実装できますか?

+0

を私は上の境界の代わりにtypeclassから始めるでしょう - それはあなたに実装の面でより多くの自由を与える – jdevelop

答えて

2

実際はそれほど難しくありません。タプル(Generic.Aux[Tup, L])からHListを取得し、HlistToList[L, AutoCloseable])からList[AutoClosable]を取得する方法が必要です。

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リソースの場合のために、あなたがcase Tuple1(f)ようTuple1(new Foo)とパターンマッチを記述する必要があるということです。最も簡単な解決策は、Loan1の部分を維持して、Loan2の部分をシェイプレスで実装されたLoanNに置き換え、すべてのアリティ> 1で動作させることです。だから、あなたの中に私の解決策を貼り付けてLoanNに私のLoanクラスの名前を変更コピーすることはほぼ同等です:

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[AutoClosable]のサブタイプであるList[Nothing]に変換することができます。

間違いなく、Loan1の追加の定型文は、1つの引数と1つの引数のタプルを区別できるより複雑な型クラスを自分自身で書くことによって、無くすことができます。


あなたは、引数としてHListを受け入れ、タプルにそれを変換するために提案しました。これも可能です(shapeless.ops.hlist.Tupler)。もちろん、そのAPIのユーザーはHListを構築しなければなりません。そして、あなたはまだTuple1をアンラップするためのきれいな構文を持たないスケーラの問題を抱えています。その第二の問題は、AからTuple1[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 
+0

それは本当にいいです、ありがとう! –

関連する問題