2015-11-05 62 views
5

在斯卡拉通用更新功能的工作,我需要創建一個產品類型&代表一個複合值,例如:實現產品類型在斯卡拉在其部分

val and: String & Int & User & ... = ??? 

and應該有一個String部分和一個Int部分和User部分裏面。這類似於斯卡拉with關鍵字:

val and: String with Int with User with ... = ??? 

有這樣的產品類型,我需要一種方法來,具有如下功能A => A,它適用於某些產品的價值,並獲得該產品回來改變A一部分。這意味着產品中的每種類型都必須是唯一的 - 這是可以接受的。

一個重要的限制是,當將一個函數A => A應用於產品時,我只知道該產品內部有A,但沒有關於其組成的其他類型的信息。但作爲該函數的調用者,我將它傳遞給一個完整類型信息的產品,並希望將這個完整類型作爲函數簽名的一部分。

在僞代碼:

def update[A, Rest](product: A & Rest, f: A => A): A & Rest 

使用無形或其他深奧的東西是沒有問題啊。我嘗試使用HList s,但它們是有序的,而像異構集合這樣的東西更適合於表示A & Rest部分。

UPDATE

這裏是低於機智加入讀取支持,一些評論認爲,解決從雷吉斯讓 - 吉爾的回答了我的使用情況下,代碼和改進類型安全:

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 

} 
+1

關於'HList'是ordere D:這是無法修復的。雖然你可以定義方法/類型類來比較兩種產品,並告訴你它們是否具有相同的類型,而不管順序如何,但當談到類型系統本身時,你是不幸的。你可以嘗試所有的魔法,你永遠不會讓編譯器認爲'&[Int,String]'和'&[String,Int]'是一樣的(這不足以實現一個完全接管的編譯器插件類型檢查這些類型,如果甚至可能的話)。 –

+0

@RégisJean-Gilles,明白了,謝謝。 – Tvaroh

回答

4

這裏是一個解決方案,只使用純scala沒有所需的庫。它使用一個相當標準的方法依賴於一個類型的類:

scala> :paste 
// Entering paste mode (ctrl-D to finish) 
case class &[L,R](left: L, right: R) 
implicit class AndOp[L](val left: L) { 
    def &[R](right: R): L & R = new &(left, right) 
} 

trait ProductUpdater[P,A] { 
    def apply(p: P, f: A => A): P 
} 
trait LowPriorityProductUpdater { 
    implicit def noopValueUpdater[P,A]: ProductUpdater[P,A] = { 
    new ProductUpdater[P,A] { 
     def apply(p: P, f: A => A): P = p // keep as is 
    } 
    } 
} 
object ProductUpdater extends LowPriorityProductUpdater { 
    implicit def simpleValueUpdater[A]: ProductUpdater[A,A] = { 
    new ProductUpdater[A,A] { 
     def apply(p: A, f: A => A): A = f(p) 
    } 
    } 
    implicit def productUpdater[L, R, A](
    implicit leftUpdater: ProductUpdater[L, A], rightUpdater: ProductUpdater[R, A] 
): ProductUpdater[L & R, A] = { 
    new ProductUpdater[L & R, A] { 
     def apply(p: L & R, f: A => A): L & R = &(leftUpdater(p.left, f), rightUpdater(p.right, f)) 
    } 
    } 
} 
def update[A,P](product: P)(f: A => A)(implicit updater: ProductUpdater[P,A]): P = updater(product, f) 
// Exiting paste mode, now interpreting. 

測試一下:

scala> case class User(name: String, age: Int) 
defined class User 

scala> val p: String & Int & User & String = "hello" & 123 & User("Elwood", 25) & "bye" 
p: &[&[&[String,Int],User],String] = &(&(&(hello,123),User(Elwood,25)),bye) 

scala> update(p){ i: Int => i + 1 } 
res0: &[&[&[String,Int],User],String] = &(&(&(hello,124),User(Elwood,25)),bye) 

scala> update(p){ s: String => s.toUpperCase } 
res1: &[&[&[String,Int],User],String] = &(&(&(HELLO,123),User(Elwood,25)),BYE) 

scala> update(p){ user: User => 
    | user.copy(name = user.name.toUpperCase, age = user.age*2) 
    | } 
res2: &[&[&[String,Int],User],String] = &(&(&(hello,123),User(ELWOOD,50)),bye) 

更新:回覆:

是否有可能當產品不包含更新值時,不能編譯

是的,這是絕對可能的。我們可以改變ProductUpdater型類,但在這種情況下,我覺得更容易引進一個單獨的類型類ProductContainsType作爲證據證明某一產品P包含A類型的至少一個元素:

scala> :paste 
// Entering paste mode (ctrl-D to finish) 

@annotation.implicitNotFound("Product ${P} does not contain type ${A}") 
abstract sealed class ProductContainsType[P,A] 
trait LowPriorityProductContainsType { 
    implicit def compositeProductContainsTypeInRightPart[L, R, A](
    implicit rightContainsType: ProductContainsType[R, A] 
): ProductContainsType[L & R, A] = null 
} 
object ProductContainsType extends LowPriorityProductContainsType { 
    implicit def simpleProductContainsType[A]: ProductContainsType[A,A] = null 
    implicit def compositeProductContainsTypeInLeftPart[L, R, A](
    implicit leftContainsType: ProductContainsType[L, A] 
): ProductContainsType[L & R, A] = null 
} 
// Exiting paste mode, now interpreting. 

現在我們可以定義我們嚴格update方法:

def strictUpdate[A,P](product: P)(f: A => A)(
    implicit 
    updater: ProductUpdater[P,A], 
    containsType: ProductContainsType[P,A] 
): P = updater(product, f) 

讓我們來看看:

scala> strictUpdate(p){ s: String => s.toUpperCase } 
res21: &[&[&[String,Int],User],String] = &(&(&(HELLO,123),User(Elwood,25)),BYE) 

scala> strictUpdate(p){ s: Symbol => Symbol(s.name.toUpperCase) } 
<console>:19: error: Product &[&[&[String,Int],User],String] does not contain type Symbol 
       strictUpdate(p){ s: Symbol => Symbol(s.name.toUpperCase) } 
+0

如何爲具有'A'和其他東西的產品編寫類型?像'val pp:User&Any = p'或類似的例子。 – Tvaroh

+1

你不能寫這樣的類型,但是你可以要求一個證據(一個隱含的值),即一個產品類型至少有一個給定類型的元素。無論如何,我不確定它有多大的價值,因爲在'update'中你想簡單地忽略不是'A'類型的元素。我想你的意圖是避免錯誤,如果你認爲'update'會更新產品的至少一個元素,但它不是因爲產品沒有'A'類型的元素,那麼你必須認識到它只會覆蓋部分可能出現的錯誤:您可能還打算更新兩個元素,但只有一個元素的類型爲「A」。 –

+0

當產品不包含要更新的值時,是否可以使其不能編譯? – Tvaroh

0

作爲一個簡單的想法,你可以做這樣的事情:

scala> case class And[A, B](first: A, second: B) 
defined class And 

scala> val x: String And Double And Int = And(And("test", 1.1), 10) 
x: And[And[String,Double],Int] = And(And(test,1.1),10) 

scala> x.copy(second = 100) 
res0: And[And[String,Double],Int] = And(And(test,1.1),100) 

當然你也可以用這樣的親定義函數管道:

def update(product: String And Int, f: String => String): String And Int 
3

不是一個最佳的變體,在我看來,@TravisBrown或@MilesSabin可以提供更完整的答案。

在示例中,我們將使用shapeless 2.2.5。 因此,我們可以將必要的類型表示爲HList(不存在任何問題)。由於這是一個HList有可能應用Poly功能:

trait A 
def aFunc(a: A) = a 

trait lowPriority extends Poly1 { 
    implicit def default[T] = at[T](poly.identity) 
} 

object polyApplyToTypeA extends lowPriority { 
    implicit def caseA = at[A](aFunc(_)) 
} 

list.map(polyApplyToTypeA) //> applies only to type A 

這是第一種方法,使用它,我們只能使用特殊Poly功能(也可以生成),其實,這是一個問題。

第二種方法是定義一個自己的功能,其具有有點困難邏輯:由類型

def applyToType[L <: HList, P <: HList, PO <: HList, S <: HList, F] 
(fun: F => F, l: L) 
(implicit partition: Partition.Aux[L, F, P, S], 
       tt: ToTraversable.Aux[P, List, F], 
       ft: FromTraversable[P], 
        p: Prepend.Aux[S, P, PO], 
        a: Align[PO, L]): L = 
(l.filterNot[F] ::: l.filter[F].toList[F].map(fun).toHList[P].get).align[L] 

此函數濾波器HList,將其轉換爲List,適用我們的功能,並將其轉換回到HList,也對齊類型,以便不改變HList類型對齊。按預期工作。完整的例子在這裏:https://gist.github.com/pomadchin/bf46e21cb180c2a81664

+0

第二種方法非常有趣(+1)。是否有可能推廣到一個函數f:F => A'?然後在某些情況下,一個標準的單形函數可以做到,而不需要一個「Poly」。 –