如何使用默认值最佳地构造无形记录?

时间:2016-08-14 21:18:22

标签: scala shapeless

说我有一个没有形状的记录:

trait T
case object Ti extends T
case object Ta extends T
case object To extends T

type R = Record.`Ta -> Option[String], Ti -> Option[Int]`.T
val r: R = (Ta ->> Option("plif")) :: (Ti ->> Option(4)) :: HNil

我想写一个函数get,以便:

get(Ta) == Some("plif")
get(Ti) == Some(4)
get(To) == None

实现这一目标的最佳方式是什么?

1 个答案:

答案 0 :(得分:1)

一个简单的解决方案是为默认情况提供您自己的Selector实例:

class DefaultSelector[R <: HList, K] extends Selector[R, K] {
  type Out = Option[Nothing]
  def apply(l: R): Out = None
}

def get[K, V](k: K)(
  implicit sel: Selector[R, K] = new DefaultSelector[R, K]
): sel.Out = sel(r)

但是使用该代码,Scala的编译器可能难以为默认情况的结果提供TypeTag

所以要解决这个问题,你也可以写一个新的类型类DefaultSelector,如果找不到None: Option[Nothing],它将默认为Selector

import shapeless._
import shapeless.ops.record._

trait DefaultSelector[R <: HList, K] {
  type Out
  def apply(r: R): Out
}

sealed trait LowPriorityDefaultSelector {
  type Aux[R <: HList, K, V] = DefaultSelector[R, K] { type Out = V }

  case class Impl[R <: HList, K, V](get: R => V) extends DefaultSelector[R, K] {
    type Out = V
    def apply(r: R): Out = get(r)
  }

  implicit def default[R <: HList, K, V](
    implicit ev: Option[Nothing] =:= V  // tricking Scala's implicit resolution
  ): Aux[R, K, V] =
    Impl[R, K, V](Function.const(None))
}

object DefaultSelector extends LowPriorityDefaultSelector {
  implicit def existing[R <: HList, K, V](
    implicit sel: Selector.Aux[R, K, V]
  ): Aux[R, K, V] =
    Impl[R, K, V](sel.apply)
}

然后get函数变为:

def get[K, V](k: K)(implicit sel: DefaultSelector[R, K]): sel.Out = sel(r)

结果(两种解决方案)都是:

scala> get(Ti)
res0: Option[Int] = Some(4)

scala> get(Ta)
res1: Option[String] = Some(plif)

scala> get(To)
res2: Option[Nothing] = None