使用shapeless HList调用一个Scala Function2,其中值与参数顺序不匹配

3
我希望建立相应的功能:

我想要构建与以下等效的功能:

def applyWithHList2[A1, A2, R, L <: HList](l: L, f: Function2[A1, A2, R]): Try[R]
  • 该列表中的值是这样的,即在N选2个可能的l.unify值组合中,最多只有一个可以用来调用函数。没有其他类型信息可用。
  • 如果没有办法调用函数,则结果应为FailureMatchError。否则,结果应为Try(f(a1, a2))
  • 我仍然在适应shapeless,并希望得到如何解决此问题的建议。
1个回答

3
有趣的是,如果HList中没有适当类型的元素可用,编写一个不会编译的版本要容易得多。
import shapeless._, ops.hlist.Selector

def applyWithHList2[A1, A2, R, L <: HList](l: L, f: (A1, A2) => R)(implicit
  selA1: Selector[L, A1],
  selA2: Selector[L, A2]
): R = f(selA1(l), selA2(l))

如果您确实希望在没有适用的配对时(在Try中)出现运行时错误,那么可以使用默认的null实例技巧:

import scala.util.{ Failure, Success, Try }

def applyWithHList2[A1, A2, R, L <: HList](l: L, f: (A1, A2) => R)(implicit
  selA1: Selector[L, A1] = null,
  selA2: Selector[L, A2] = null
): Try[R] = Option(selA1).flatMap(s1 =>
  Option(selA2).map(s2 => f(s1(l), s2(l)))
).fold[Try[R]](Failure(new MatchError()))(Success(_))

如果你觉得这不太好(确实如此),你可以使用自定义类型类:

trait MaybeSelect2[L <: HList, A, B] {
  def apply(l: L): Try[(A, B)] = (
    for { a <- maybeA(l); b <- maybeB(l) } yield (a, b)
  ).fold[Try[(A, B)]](Failure(new MatchError()))(Success(_))

  def maybeA(l: L): Option[A]
  def maybeB(l: L): Option[B]
}

object MaybeSelect2 extends LowPriorityMaybeSelect2 {
  implicit def hnilMaybeSelect[A, B]: MaybeSelect2[HNil, A, B] =
    new MaybeSelect2[HNil, A, B] {
      def maybeA(l: HNil): Option[A] = None
      def maybeB(l: HNil): Option[B] = None
    }

  implicit def hconsMaybeSelect0[H, T <: HList, A](implicit
    tms: MaybeSelect2[T, A, H]
  ): MaybeSelect2[H :: T, A, H] = new MaybeSelect2[H :: T, A, H] {
    def maybeA(l: H :: T): Option[A] = tms.maybeA(l.tail)
    def maybeB(l: H :: T): Option[H] = Some(l.head)
  }

  implicit def hconsMaybeSelect1[H, T <: HList, B](implicit
    tms: MaybeSelect2[T, H, B]
  ): MaybeSelect2[H :: T, H, B] = new MaybeSelect2[H :: T, H, B] {
    def maybeA(l: H :: T): Option[H] = Some(l.head)
    def maybeB(l: H :: T): Option[B] = tms.maybeB(l.tail)
  }
}

trait LowPriorityMaybeSelect2 {
  implicit def hconsMaybeSelect2[H, T <: HList, A, B](implicit
    tms: MaybeSelect2[T, A, B]
  ): MaybeSelect2[H :: T, A, B] = new MaybeSelect2[H :: T, A, B] {
    def maybeA(l: H :: T): Option[A] = tms.maybeA(l.tail)
    def maybeB(l: H :: T): Option[B] = tms.maybeB(l.tail)
  }
}

然后:

def applyWithHList2[A1, A2, R, L <: HList](l: L, f: (A1, A2) => R)(implicit
  ms2: MaybeSelect2[L, A1, A2]
): Try[R] = ms2(l).map(Function.tupled(f))

但这样做太麻烦了,只是为了放弃一些编译时的安全性。

请注意,这些方法都没有强制执行函数可以应用于HList中最多只有一对元素的约束条件,因为我将其视为您问题中的前提条件。肯定有可能编写一个在编译时强制执行该约束条件的解决方案(甚至可能比上面的MaybeSelect2实现更短)。


编译时失败甚至比运行时失败更好。在编译时强制执行匹配约束(只有一个N选2值类型对与(A1,A2)匹配)的想法也非常有趣(从信任但验证前提条件的角度来看)。如果你愿意考虑这个问题,我会创建一个单独的、更具体的问题。 - Sim
@Sim 当然,我认为那将是一个很好的问题。 - Travis Brown

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