In Scala, devo generare un tipo di prodotto &
che rappresenta un valore composto, per es .:Implementare tipo di prodotto in Scala con funzione di aggiornamento generico che lavora su sue parti
val and: String & Int & User & ... = ???
Vale a dire and
dovrebbe avere una parte String
e una parte Int
e una parte User
all'interno. Questo è simile alla Scala with
parola chiave:
val and: String with Int with User with ... = ???
Avere tipo di prodotto del genere ho bisogno di un modo per, avendo una funzione A => A
, applicarlo a un certo valore del prodotto e ottenere il prodotto indietro con A
parte alterata. Implica che ciascun tipo di prodotto deve essere unico - è accettabile.
Una limitazione importante è che, quando si applica una funzione A => A
al prodotto, so solo che il prodotto ha A
da qualche parte all'interno ma nessuna informazione sugli altri tipi di cui è composto. Ma come chiamante della funzione, gli passo un prodotto con informazioni di tipo completo e aspetto di ottenere questo tipo completo come parte della firma della funzione.
In pseudo-codice:
def update[A, Rest](product: A & Rest, f: A => A): A & Rest
Utilizzando Shapeless o altra roba esoterica va bene per me. Ho provato a utilizzare HList
s ma sono ordinati, mentre qualcosa come set eterogeneo sarebbe più appropriato qui per rappresentare la parte A & Rest
.
UPDATE:
Ecco il codice che risolve il mio caso d'uso tratto da Régis Jean-Gilles risposta qui sotto spirito aggiunto leggere supporto, alcuni commenti, e una migliore tipo di sicurezza:
object product {
/** Product of `left` and `right` values. */
case class &[L, R](left: L, right: R)
implicit class AndPimp[L](val left: L) extends AnyVal {
/** Make a product of `this` (as left) and `right`. */
def &[R](right: R): L & R = new &(left, right)
}
/* Updater. */
/** Product updater able to update value of type `A`. */
trait ProductUpdater[P, A] {
/** Update product value of type `A`.
* @return updated product */
def update(product: P, f: A ⇒ A): P
}
trait LowPriorityProductUpdater {
/** Non-product value updater. */
implicit def valueUpdater[A]: ProductUpdater[A, A] = new ProductUpdater[A, A] {
override def update(product: A, f: A ⇒ A): A = f(product)
}
}
object ProductUpdater extends LowPriorityProductUpdater {
/** Left-biased product value updater. */
implicit def leftProductUpdater[L, R, A](implicit leftUpdater: ProductUpdater[L, A]): ProductUpdater[L & R, A] =
new ProductUpdater[L & R, A] {
override def update(product: L & R, f: A ⇒ A): L & R =
leftUpdater.update(product.left, f) & product.right
}
/** Right-biased product value updater. */
implicit def rightProductUpdater[L, R, A](implicit rightUpdater: ProductUpdater[R, A]): ProductUpdater[L & R, A] =
new ProductUpdater[L & R, A] {
override def update(product: L & R, f: A ⇒ A): L & R =
product.left & rightUpdater.update(product.right, f)
}
}
/** Update product value of type `A` with function `f`.
* Won't compile if product contains multiple `A` values.
* @return updated product */
def update[P, A](product: P)(f: A ⇒ A)(implicit updater: ProductUpdater[P, A]): P =
updater.update(product, f)
/* Reader. */
/** Product reader able to read value of type `A`. */
trait ProductReader[P, A] {
/** Read product value of type `A`. */
def read(product: P): A
}
trait LowPriorityProductReader {
/** Non-product value reader. */
implicit def valueReader[A]: ProductReader[A, A] = new ProductReader[A, A] {
override def read(product: A): A = product
}
}
object ProductReader extends LowPriorityProductReader {
/** Left-biased product value reader. */
implicit def leftProductReader[L, R, A](implicit leftReader: ProductReader[L, A]): ProductReader[L & R, A] =
new ProductReader[L & R, A] {
override def read(product: L & R): A =
leftReader.read(product.left)
}
/** Right-biased product value reader. */
implicit def rightProductReader[L, R, A](implicit rightReader: ProductReader[R, A]): ProductReader[L & R, A] =
new ProductReader[L & R, A] {
override def read(product: L & R): A =
rightReader.read(product.right)
}
}
/** Read product value of type `A`.
* Won't compile if product contains multiple `A` values.
* @return value of type `A` */
def read[P, A](product: P)(implicit productReader: ProductReader[P, A]): A =
productReader.read(product)
// let's test it
val p = 1 & 2.0 & "three"
read[Int & Double & String, Int](p) // 1
read[Int & Double & String, Double](p) // 2.0
read[Int & Double & String, String](p) // three
update[Int & Double & String, Int](p)(_ * 2) // 2 & 2.0 & three
update[Int & Double & String, Double](p)(_ * 2) // 1 & 4.0 & three
update[Int & Double & String, String](p)(_ * 2) // 1 & 2.0 & threethree
}
Chi 'HList' essere ordere d: questo non può essere risolto, mai. Mentre è possibile definire metodi/classi di tipi che confronteranno due prodotti e diranno se hanno lo stesso tipo indipendentemente dall'ordinamento, quando si tratta del sistema di tipo in sé, si è sfortunati. Puoi provare ogni magia, non sarai mai in grado di far pensare al compilatore che '& [Int, String]' sia lo stesso di '& [String, Int]' (a meno di implementare un plug-in del compilatore che prenderebbe interamente il sopravvento il tipo di controllo per quei tipi, se è anche possibile). –
@ RégisJean-Gilles, capito, grazie. – Tvaroh