Может ли Карта выполняться в Scala HList
Теперь я сделал несколько реализаций HList. Один из них основан на Данииле Спиваке Высоком Волшебстве в Стране Scala, а другой основан на записи в блоге Apocalisp. Цель состояла в том, чтобы иметь гетерогенный список, который не является гетерогенным в основном типе, а скорее более высоким. Например:
val requests = Request[String] :: Request[Int] :: HNil
Я мог бы сделать карту в списке для выполнения запроса и привести к гетерогенному списку более высокого типа. Итак:
requests.map(execute)
должен равняться
String :: Int :: HNil
К сожалению, все мои попытки привели к HList Any. Вот код из недавней попытки:
class Request[+Out](o:Out) {
type O = Out
def v:O = o
}
object HList {
trait Func[-Elem,Out] {
type Apply[E <: Elem] <: Out
def apply[N <: Elem](e:N):Apply[N]
}
sealed trait HList[Base] {
type Head <: Base
type Tail <: HList[Base]
type Map[Out,F <: Func[Base,Out]] <: HList[Out]
def head:Head
def tail:Tail
def ::[A <: Base](a:A):HList[Base]
def map[Out,F <: Func[Base,Out]](f:F):Map[Out,F]
}
case class HNil[Base]() extends HList[Base] {
type Head = Nothing
type Tail = Nothing
type Map[Out,F <: Func[Base,Out]] = HNil[Out]
def head = error("Head of an empty HList")
def tail = error("Head of an empty HList")
def ::[A <: Base](a:A) = HCons(a,this)
def map[Out,F <: Func[Base,Out]](f:F) = new HNil[Out]
}
case class HCons[Base,A <: Base,B <: HList[Base]](head: A, tail: B) extends HList[Base] {
type Head = A
type Tail = B
type Map[Out,F <: Func[Base,Out]] = HCons[Out,F#Apply[Head],Tail#Map[Out,F]]
def ::[C <: Base](c:C) = HCons(c,this)
def map[Out,F <: Func[Base,Out]](f:F) =
HCons(f(head),tail.map(f))
}
val :: = HCons
}
object Test extends Application {
import HList._
val HNil = new HNil[Request[_]]
val list = new Request[Int](1) :: new Request[String]("1") :: HNil
val (a :: b :: HNil) = list
val y:Request[String] = b
val results = list.map[Any,Unwrap.type](Unwrap)
val i:Int = results.head
}
import HList._
object Unwrap extends Func[Request[Any],Any] {
type Apply[I <: Request[Any]] = I#O
def apply[N <: Request[Any]](e:N) = null.asInstanceOf[Apply[N]]
}
Другая попытка была основана на версии Apocalisp, которая использует fold для создания нового HList, и снова это привело к HList любых типов. Любые советы будут оценены.
Ответы
Ответ 1
Реализация HList
в shapeless достаточно богата, чтобы использовать функции HList
и KList
. Он предоставляет операцию map
, которая применяет функцию с более высоким рейтингом, возможно, с конкретными типами случаев, через ее элементы, дающие результат с соответствующим типом HList
,
import shapeless.Poly._
import shapeless.HList._
// Define a higher-ranked function from Sets to Options
object choose extends (Set ~> Option) {
def default[T](s : Set[T]) = s.headOption
}
// An HList of Sets
val sets = Set(1) :: Set("foo") :: HNil
// Map our choose function across it ...
val opts = sets map choose
// The resulting value
opts == Option(1) :: Option("foo") :: HNil
Обратите внимание, что хотя в приведенном выше примере нет требования о том, чтобы элементы HList имели общий внешний конструктор внешнего типа, это должно быть только в том случае, если функция с более высоким рангом, сопоставленная с, имеет случаи для всех задействованных типов,
// size is a higher-ranked function from values of arbitrary type to a 'size'
// which is defined as 1 by default but which has type specific cases for
// Strings and tuples
object size extends (Id ~> Const[Int]#λ) {
def default[T](t : T) = 1
}
implicit def sizeString = size.λ[String](s => s.length)
implicit def sizeTuple[T, U](implicit st : size.λ[T], su : size.λ[U]) =
size.λ[(T, U)](t => 1+size(t._1)+size(t._2))
size(23) == 1 // Default
size("foo") == 3 // Type specific case for Strings
size((23, "foo")) == 5 // Type specific case for tuples
Теперь сопоставьте это через HList
,
val l = 23 :: true :: "foo" :: ("bar", "wibble") :: HNil
val ls = l map size
ls == 1 :: 1 :: 3 :: 10 :: HNil
В этом случае тип результата отображаемой функции является константой: it Int, независимо от типа аргумента. Следовательно, полученный HList имеет элементы одинакового типа, что означает, что его можно с успехом преобразовать в список ванили,
ls.toList == List(1, 1, 3, 10)
Ответ 2
вам нужен Klist с конструктором типа Request
и естественным преобразованием execute: Request ~> Id
. Все это подробно описано в чудесной серии программных рядов сообщений в Apocalisp, в частности:
вы можете проверить код для всей серии из Отметить Harrah up repo
В вашем случае вам понадобится что-то вроде
val reqList = new Request[Int](1) :^: new Request[String]("1") :^: KNil
val exec = new (Request ~> Id) { def apply[T](reqs: Request[T]): T = reqs.execute }
val results = reqList down exec
приведенный выше метод down
концептуально совпадает с map
для nat transf M ~> Id
; у вас также есть более общий map
, который из nat transf M ~> N
и Klist вида M дает KList вида N.
Ответ 3
Обратите внимание, что у вас есть пример Карты с HList в недавней статье (октябрь 2016 года, 5 лет после OP) " Использование бесформенных" HLists "для безопасности дополнительного типа (в потоках Akka) от Mikołaj Koziarkiewicz.
//glue for the ParserStageDefs
specs.map(s => Flow[Data].map(s.parser).map(s.processor))
.foreach(broadcast ~> _ ~> merge)
Проблема заключается в том, что информация о типе в нашем списке спецификаций не сохраняется. Вернее, не сохранились так, как мы хотим - тип List
элементов ParserStageDef[_ >: Int with String]
, поэтому самый низкий общий супертип для нашего декоратора и приращения.
Из сказанного следует, что при сопоставлении парсера и процессорных элементов компилятор не имеет возможности предоставить фактический тип T
, который используется в данной спецификации.
Решение
Здесь, где HLists приходят на помощь. Поскольку они сохраняют полную информацию о типе для каждого элемента, можно определить наш поток очень похоже на нашу последнюю попытку.
Сначала заменим наш список на HList
:
import shapeless.ops.hlist._
import shapeless._
//...
val specs = decorator :: incrementer :: HNil
val specsSize = specs.length.toInt
Теперь, для отображения из ParserStageDefs
в Flows
, нам нужно использовать другой подход, так как для map
для HList
требуется нечто, называемое P ** oly - значение полиморфной функции **.
Вот как бы выглядели в нашем случае:
import shapeless.PolyDefns.~>
object toFlow extends (ParserStageDef ~> ProcessingFlow) {
override def apply[T](f: ParserStageDef[T]) =
Flow[Data].map(f.parser).map(f.processor)
}
Для этого у нас также будет изменение ProcessingFlow
, чтобы набрать ProcessingFlow[_] = Flow[Data, Data, _]
, так как вышеописанная полиморфная функция ожидает более высокого типа.
Теперь наш центральный оператор оказывается:
//we convert to a List[ProcessingFlow[_]] for simplicity
specs.map(toFlow).toList.foreach(broadcast ~> _ ~> merge)
и мы все настроены!