2016-04-09 2 views
7

У меня есть следующая проблема: я хочу сопоставить элементы HList с другим HList, но строки в исходном HList должны быть преобразованы только в URL, если тип «target» - это URL.Shapeless map HList в зависимости от типов целей

val name = "Stackoverflow" 
val url = "https://stackoverflow.com/q" 
val list = name :: url :: HNil 

val mapped: String :: URL :: HNil = list.map(???) 

Насколько я понимаю, все, что касается Poly, заботится только о типе ввода, но не о типе выхода. Так есть способы заархивировать мою цель?

ответ

8

Я не думаю, что вы получите именно то, что хотите, поскольку неявное разрешение Scala происходит до ввода типа (но кто знает - люди делают то, что меня удивляет в Scala все время).

(примечание к стороне: шаблон CanBuildFrom/breakOut поддерживает что-то похожее на то, о чем вы просите, но я не вижу способа заставить его работать в этой ситуации, поскольку тип источника ограничивает, какие экземпляры доступны .)

Существует довольно стандартное обходное решение для такого рода ситуаций, хотя и связано с использованием вспомогательного класса для аппроксимации частичного применения параметров типа. Предположим, что у вас есть достаточно простой класс типа, который улавливает логику преобразования:

import java.net.URL 
import shapeless._ 

trait Convert[I <: HList, O <: HList] { def apply(i: I): O } 

object Convert extends LowPriorityConvertInstances { 
    implicit val convertHNil: Convert[HNil, HNil] = new Convert[HNil, HNil] { 
    def apply(i: HNil): HNil = i 
    } 

    implicit def convertHConsURL[T <: HList, TO <: HList](implicit 
    c: Convert[T, TO] 
): Convert[String :: T, URL :: TO] = new Convert[String :: T, URL :: TO] { 
    def apply(i: String :: T): URL :: TO = new URL(i.head) :: c(i.tail) 
    } 
} 

sealed class LowPriorityConvertInstances { 
    implicit def convertHCons[H, T <: HList, TO <: HList](implicit 
    c: Convert[T, TO] 
): Convert[H :: T, H :: TO] = new Convert[H :: T, H :: TO] { 
    def apply(i: H :: T): H :: TO = i.head :: c(i.tail) 
    } 
} 

Теперь вы можете попробовать что-то вроде этого:

def convert[I <: HList, O <: HList](i: I)(implicit c: Convert[I, O]): O = c(i) 

Но есть две проблемы. Во-первых, если вы укажете параметры типа, вы всегда получите преобразование, которое превращает каждую строку в URL-адрес. Вы можете переопределить это поведение, явно предоставляя оба параметра типа, но ugh.

Мы можем (вид) улучшить эту ситуацию с вспомогательным классом:

class PartiallyAppliedConvert[O <: HList] { 
    def apply[I <: HList](i: I)(implicit c: Convert[I, O]): O = c(i) 
} 

def convert[O <: HList]: PartiallyAppliedConvert[O] = 
    new PartiallyAppliedConvert[O] 

Теперь вы можете написать следующее:

scala> val mapped = convert[String :: URL :: HNil](list) 
mapped: shapeless.::[String,shapeless.::[java.net.URL,shapeless.HNil]] = Stackoverflow :: https://stackoverflow.com/q :: HNil 

Это не совсем то, что вы просили, но это довольно close, поскольку единственным типом, который мы должны явно указывать, является желаемый тип цели.

+0

Это в значительной степени то, о чем я просил, я просто не знал, как будет выглядеть конец. Благодаря! –

Смежные вопросы