[英]Generic transform/fold/map over tuple/hlist containing some F[_]
我最近詢問了Map並減少/折疊scalaz.Validation的HList,並得到了一個很好的答案,如何將一個固定大小的Va[T]
元組(這是scalaz.Validation[String, T]
的別名)轉換為scalaz.ValidationNel[String, T]
。 從那以后,我一直在研究無形和類型級編程,試圖找到適用於任何大小的元組的解決方案。
這就是我剛開始的時候:
import scalaz._, Scalaz._, shapeless._, contrib.scalaz._, syntax.std.tuple._
type Va[A] = Validation[String, A]
// only works on pairs of Va[_]
def validate[Ret, In1, In2](params: (Va[In1], Va[In2]))(fn: (In1, In2) => Ret) = {
object toValidationNel extends Poly1 {
implicit def apply[T] = at[Va[T]](_.toValidationNel)
}
traverse(params.productElements)(toValidationNel).map(_.tupled).map(fn.tupled)
}
那么validate
是一個幫手,我這樣稱呼:
val params = (
postal |> nonEmpty[String]("no postal"),
country |> nonEmpty[String]("no country") >=> isIso2Country("invalid country")
)
validate(params) { (postal, country) => ... }
我開始采用任何Product
而不是一對,並將其內容限制為Va[T]
:
// needs to work with a tuple of Va[_] of arbitrary size
def validateGen[P <: Product, F, L <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, L],
va: UnaryTCConstraint[L, Va],
fp: FnToProduct.Aux[F, L => R]
) = ???
我確實覺得簡單地添加約束只能確保輸入有效,但對實現函數體沒有任何幫助,但我不知道如何去糾正它。
然后traverse
開始抱怨缺少證據,所以我最終得到:
def validateGen[P <: Product, F, L <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, L],
va: UnaryTCConstraint[L, Va],
tr: Traverser[L, toValidationNel.type],
fp: FnToProduct.Aux[F, L => R]
) = {
traverse(gen.to(params): HList)(toValidationNel).map(_.tupled).map(block.toProduct)
}
然而,編譯器繼續抱怨缺少Traverser[HList, toValidationNel.type]
隱式參數,即使它存在。
我需要向編譯器提供哪些額外的證據才能進行traverse
調用以進行編譯? 它是否與UnaryTCConstraint
沒有以對traverse
調用有用的方式聲明,即它不能將toValidationNel
應用於params
因為它不能證明params
只包含Va[_]
?
PS我還發現了leftReduce無形HList的泛型類型並嘗試使用foldRight
而不是traverse
無濟於事; 在嘗試診斷編譯器確實缺少哪些證據時,錯誤消息並沒有太大幫助。
更新:
根據lmm指出的內容,我已經將轉換刪除到了HList
,但是現在的問題是,在非泛型解決方案中,我可以調用.map(_.tupled).map(block.toProduct)
traverse
調用的結果,我現在得到:
value map不是shapeless.contrib.scalaz.Out的成員
為什么有可能在traverse(params.productElements)(toValidationNel)
調用的結果而不是通用遍歷?
更新2:
將Traverser[...]
位更改為Traverser.Aux[..., Va[L]]
有助於編譯器找出遍歷的預期結果類型,但是,這只會使validateGen
函數成功編譯但會產生另一個錯誤在通話現場:
[error] could not find implicit value for parameter tr: shapeless.contrib.scalaz.Traverser.Aux[L,toValidationNel.type,Va[L]]
[error] validateGen(params) { (x: String :: String :: HNil) => 3 }
[error] ^
我也覺得UnaryTCConstraint
是完全沒必要的 - 但我仍然對Shapeless太新了,不知道是不是這樣。
更新3:
已經意識到遍歷的類型不能是Va[L]
因為L
本身已經是Va[_]
的hlist,我將L
類型參數拆分為In
和Out
:
def validateGen[P <: Product, F, In <: HList, Out <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, In],
va: UnaryTCConstraint[In, Va], // just for clarity
tr: Traverser.Aux[In, toValidationNel.type, Va[Out]],
fn: FnToProduct.Aux[F, Out => R]
): Va[R] = {
traverse(gen.to(params))(toValidationNel).map(block.toProduct)
}
這編譯得很好 - 我很想知道以前版本的Va[L]
是如何返回值(即Traverser.Aux
的第3個參數)甚至編譯 - 但是,在調用網站,我現在得到:
未指定的值參數tr,fn
你有一個Traverser[L, toValidationNel.type]
與Traverser[HList, toValidationNel.type]
(它必須適用於任何HList
- 沒有機會)。 我不知道為什么你寫了gen.to(params): HList
,但這會丟掉類型信息; 不應該是L
型?
這可能只會使問題更高一級; 我懷疑你能否自動獲得你需要的Traverser
。 但是你應該能夠編寫一個隱式方法,根據UnaryTCConstraint
提供一個方法,並且它可能是無形的,已經包含了它並且它將是Just Work。
更新:
在第一個示例中,編譯器知道它正在使用的特定Traverser
實例,因此它知道Out
類型是什么。 在validateGen
您沒有約束任何關於tr.Out
,因此編譯器無法知道它是支持.map
的類型。 如果你知道遍歷的輸出需要什么,那么你可能需要一個合適的Traverser.Aux
即:
tr: Traverser.Aux[L, toValidationNel.type, Va[L]]
(只是不要問我如何確保類型推斷仍然有效)。
我想你可能不想要.map(_.tupled)
,因為_
已經有一個HList
(我懷疑它在原始validate
也是多余的),但是我從來沒有使用過.toProduct
,所以也許你有對的。
更新2:
對,這是我最初懷疑的。 綜觀實行Sequencer
我懷疑你是正確的和UnaryTCConstraint
將由被包含Traverser
。 如果你沒有使用它,那么沒有必要。
我能給出的唯一建議是追逐應該提供你的暗示的電話。 例如, Traverser
應來自Traverser.mkTraverser
。 因此,如果您嘗試調用Traverser.mkTraverser[String :: String :: HNil, toValidationNel.type, Va[String] :: Va[String] :: HNil]
那么您應該能夠看到它是Mapper還是Sequencer無法找到。 然后你可以通過隱式調用來遞歸,直到你找到一個應該工作的更簡單的情況,但事實並非如此。
經過長時間的實驗,挫折和死腦細胞,我從頭開始沒有Traverser
,而是選擇Mapper
和Sequencer
; 我稍后會試着看看我是否可以再次使用Traverser
(如果不是為了實用性,至少為了學習目的):
def validate[P <: Product, F, L1 <: HList, L2 <: HList, L3 <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, L1],
mp: Mapper.Aux[toValidationNel.type, L1, L2],
seq: Sequencer.Aux[L2, VaNel[L3]],
fn: FnToProduct.Aux[F, L3 => R]
): VaNel[R] = {
sequence(gen.to(params).map(toValidationNel)).map(block.toProduct)
}
這是證明 - 雙關語意圖 - 它運行http://www.scastie.org/7086 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.