2015-11-05 4 views
5

に取り組んでScalaで製品タイプを実装し、Iは、化合物の値を表す製品タイプ&、例えば:ジェネリック更新機能は、Scalaでその部品

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

すなわちを作成する必要がありますandは、String部とInt部とUser部を含む必要があります。私はいくつかの製品の値に適用し、変更されたA一部でバックその製品を取得し、機能A => Aを持つ、への道を必要とするような製品の種類を持つ

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

:これはScalaのwithキーワードに似ています。これは、製品の各タイプが一意でなければならないことを意味します。

重要な制限の1つは、製品にA => Aという機能を適用すると、その製品の内部にはAが含まれていることがわかりますが、他のタイプの情報はありません。しかし、関数の呼び出し元として、完全な型の情報を持つ製品を渡し、この完全型を関数の署名の一部として返すことを期待しています。擬似コードで

型崩れまたは他の難解なものを使用して

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

が私のために大丈夫です。私はHListを使ってみましたが、それらは順序付けされていますが、異種セットのようなものは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

をd:これは決して固定することはできません。 2つの製品を比較するメソッド/タイプクラスを定義して、順序に関係なく同じタイプであるかどうかを伝えることができますが、タイプシステム自体には不運があります。あなたはすべての魔法を試すことができます、あなたは '&[Int、String]'が '&[String、Int]'と同じであるとコンパイラが考えることは決してできません(完全に引き継ぐコンパイラプラグインこれらの型の型チェック、それが可能であれば)。 –

+0

@RégisJean-Gilles、ありがとう、ありがとう。 – Tvaroh

答えて

4

ここでは、必要なライブラリを持たない純粋なスケーラだけを使用したソリューションです。それはかなり標準的な手法を使って型クラスに依存しています:

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型クラスを変更することができますが、この場合、私はそれがはるかに簡単に与えられた製品PはタイプAの少なくとも一つの要素が含まれていることを証拠として分離型クラスProductContainsTypeを紹介して見つける:

今、私たちはできる
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) 

は見てみましょう: `HList`さordereについて

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

このような型は記述できませんが、製品型に少なくとも1つの型の要素があることを示す証拠(暗黙の値)を要求することができます。とにかく、 'update'では単に' A'型ではない要素を無視したいと考えています。私はあなたの意図は、 'update'が製品の少なくとも1つの要素を更新すると思われるエラーを避けることだと思いますが、製品には' A'型の要素がないので、それだけでカバーすることを認識しなければなりません考えられるエラーの一部:2つの要素を更新する予定ですが、1つだけがタイプ「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) 

あなたは、このようなプロで関数を定義することができますダクト:

3

最適なバリアントではありません、@ TravisBrownや@MilesSabinがより完全な回答を提供できるようです。

例では、shapeless 2.2.5を使用します。 したがって、必要なタイプをHList(アリティー問題なし)として表すことができます。 Poly機能を適用することが可能であるHListあるとして:

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機能を使用する必要があり、それを使用して、最初のアプローチだったこと、実際には、それが問題です。

第二のアプローチは少し難しいロジックを持っている独自の機能、定義することである:Listに変換し、種類によってこの関数フィルタにHList

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に変更し、HListタイプのアライメントを変更しないように、タイプを揃えます。期待どおりに動作します。完全な例:https://gist.github.com/pomadchin/bf46e21cb180c2a81664

+0

2番目のアプローチはかなり面白いです(+1)。 'f:F => A'関数に一般化することは可能でしょうか?そして、ある場合には、標準的な単形関数が「ポリ」を必要とせずに行うでしょう。 –