Scala контекстные границы
Используя границы контекста в Scala, вы можете делать такие вещи, как
trait HasBuild[T] {
def build(buildable: T): Something
}
object Builders {
implict object IntBuilder extends HasBuild[Int] {
override def build(i: Int) = ??? // Construct a Something however appropriate
}
}
import Builders._
def foo[T: HasBuild](input: T): Something = implicitly[HasBuild[T]].build(1)
val somethingFormInt = foo(1)
Или просто
val somethingFromInt = implicitly[HasBuild[Int]].build(1)
Как я мог выразить тип Seq
любых элементов, которые имеют соответствующие неявные HasBuild
объект в области? Возможно ли это без особых волшебных и внешних библиотек?
Seq[WhatTypeGoesHere]
- Я должен быть в состоянии найти подходящий HasBuild
для каждого элемента
Это, очевидно, не компилируется:
val buildables: Seq[_: HasBuild] = ???
2 ответа
По сути, я хотел бы иметь возможность обрабатывать несвязанные типы обычным способом (например, сборка), без того, чтобы пользователь обернул их в какой-то адаптер вручную - и принудительно установил компилятором, что типы действительно могут обрабатываться. Не уверен, что цель ясна.
Что вы можете сделать:
case class HasHasBuild[A](value: A)(implicit val ev: HasBuild[A])
object HasHasBuild {
implicit def removeEvidence[A](x: HasHasBuild[A]): A = x.value
implicit def addEvidence[A: HasBuild](x: A): HasHasBuild[A] = HasHasBuild(x)
}
и сейчас (при условии, что вы добавляете HasBuild[String]
для демонстрации):
val buildables: Seq[HasHasBuild[_]] = Seq(1, "a")
компилируется, но
val buildables1: Seq[HasHasBuild[_]] = Seq(1, "a", 1.0)
не делает. Вы можете использовать методы с неявным HasBuild
параметры, когда у вас есть только HasHasBuild
:
def foo1[A](x: HasHasBuild[A]) = {
import x.ev // now you have an implicit HasBuild[A] in scope
foo(x.value)
}
val somethings: Seq[Something] = buildables.map(foo1(_))
Перво-наперво, вопреки некоторым комментариям, вы полагаетесь на границы контекста. Запрос неявного экземпляра класса типа для T
это то, что вы называете "привязанным к контексту".
То, что вы хотите, достижимо, но не тривиально и, конечно, не без других библиотек.
import shapeless.ops.hlist.ToList
import shapeless._
import shapeless.poly_
object builder extends Poly1 {
implicit def caseGeneric[T : HasBuilder] = {
at[T](obj => implicitly[HasBuilder[T]].build(obj))
}
}
class Builder[L <: HList](mappings: L) {
def build[HL <: HList]()(
implicit fn: Mapper.Aux[builder.type, L, HL],
lister: ToList[Something]
) = lister(mappings map fn)
def and[T : HasBuilder](el: T) = new Builder[T :: L](el :: mappings)
}
object Builder {
def apply[T : HasBuilder](el: T) = new Builder(el :: HNil)
}
Теперь вы можете сделать что-то вроде:
Builder(5).and("string").build()
Это вызовет build
методы из всех отдельных экземпляров классов типа implcit и предоставляют вам список результатов, где каждый результат имеет тип Something
, Он опирается на тот факт, что все методы сборки имеют нижнюю верхнюю границу Something
Например, согласно вашему примеру:
trait HasBuild[T] {
def build(buildable: T): Something
}