假設我想要在某些字符串和整數標識符之間進行映射,並且我希望我的類型能夠使運行時失敗,因爲有人試圖查找超出範圍的id。這裏有一個簡單的API:運行時值的精化和存在類型
trait Vocab {
def getId(value: String): Option[Int]
def getValue(id: Int): Option[String]
}
這是煩人,不過,如果用戶通常會從getId
獲得它們的ID,所以知道他們是有效的。以下是在這個意義上的改善:
trait Vocab[Id] {
def getId(value: String): Option[Id]
def getValue(id: Id): String
}
現在我們可以有這樣的事情:
class TagId private(val value: Int) extends AnyVal
object TagId {
val tagCount: Int = 100
def fromInt(id: Int): Option[TagId] =
if (id >= 0 && id < tagCount) Some(new TagId(id)) else None
}
然後我們的用戶可以與Vocab[TagId]
工作,而不必擔心檢查getValue
查找是否失敗在典型情況下,但他們仍然可以查找任意整數,如果他們需要。不過,它仍然非常尷尬,因爲我們必須爲每種我們想要詞彙表的東西寫一個單獨的類型。
我們也可以做這樣的事情與refined:
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Interval.ClosedOpen
import shapeless.Witness
class Vocab(values: Vector[String]) {
type S <: Int
type P = ClosedOpen[Witness.`0`.T, S]
def size: S = values.size.asInstanceOf[S]
def getId(value: String): Option[Refined[Int, P]] = values.indexOf(value) match {
case -1 => None
case i => Some(Refined.unsafeApply[Int, P](i))
}
def getValue(id: Refined[Int, P]): String = values(id.value)
}
現在雖然S
不是在編譯時已知,則編譯器仍然能夠保持的事實IDS它給我們的軌道介於零和S
之間,所以我們不必擔心在返回值時發生故障的可能性(當然,如果我們使用相同的vocab
實例)。
我想要的是能夠寫出這樣的:
val x = 2
val vocab = new Vocab(Vector("foo", "bar", "qux"))
eu.timepit.refined.refineV[vocab.P](x).map(vocab.getValue)
,使用戶可以方便地查詢任意整數,當他們真正需要的。這並不編譯,但:
scala> eu.timepit.refined.refineV[vocab.P](x).map(vocab.getValue)
<console>:17: error: could not find implicit value for parameter v: eu.timepit.refined.api.Validate[Int,vocab.P]
eu.timepit.refined.refineV[vocab.P](x).map(vocab.getValue)
^
我可以把它通過爲S
提供Witness
實例編譯:
scala> implicit val witVocabS: Witness.Aux[vocab.S] = Witness.mkWitness(vocab.size)
witVocabS: shapeless.Witness.Aux[vocab.S] = [email protected]
scala> eu.timepit.refined.refineV[vocab.P](x).map(vocab.getValue)
res1: scala.util.Either[String,String] = Right(qux)
當然失敗(在運行時,但安全),當值超出範圍:
scala> val y = 3
y: Int = 3
scala> println(eu.timepit.refined.refineV[vocab.P](y).map(vocab.getValue))
Left(Right predicate of (!(3 < 0) && (3 < 3)) failed: Predicate failed: (3 < 3).)
我也可以把證人定義我Vocab
類中,然後導入vocab._
在需要時提供它,但我真正想要的是能夠提供refineV
支持,無需額外的導入或定義。
我已經嘗試了各種這樣的東西:
object Vocab {
implicit def witVocabS[V <: Vocab](implicit
witV: Witness.Aux[V]
): Witness.Aux[V#S] = Witness.mkWitness(witV.value.size)
}
但是,這仍然需要對每個vocab
例如一個明確的定義:
scala> implicit val witVocabS: Witness.Aux[vocab.S] = Vocab.witVocabS
witVocabS: shapeless.Witness.Aux[vocab.S] = [email protected]
scala> eu.timepit.refined.refineV[vocab.P](x).map(vocab.getValue)
res4: scala.util.Either[String,String] = Right(qux)
我知道我可以用宏實現witVocabS
,但我覺得應該有更好的方法來做這種事情,因爲它看起來像是一個非常合理的用例(我對提煉不太熟悉,所以我完全有可能錯過某些明顯的東西)。
非常好,謝謝! –
雖然我認爲這樣做可以取消刪除其他答案,因爲這是一種合理的替代方法。 –