簡體   English   中英

包含一些F [_]的元組/ hlist上的通用轉換/折疊/映射

[英]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類型參數拆分為InOut

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 ,而是選擇MapperSequencer ; 我稍后會試着看看我是否可以再次使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM