2017-02-14 16 views
3

У меня есть эта версия try-with-resources в Scala. Интересно, можно ли сделать общую версию этого, используя Shapeless и HList?Convert Shapeless HList to the Tuple

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

На самом деле это не так сложно. Вам нужен способ получить HList из кортежа (Generic.Aux[Tup, L]) и способ получить List[AutoClosable] от Hlist (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 и заменить часть Loan2 на LoanN, которая реализована бесформенной и работает для каждой цели> 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 
+0

Это действительно приятно, спасибо! –