2012-01-11 35 views
20

Tôi đang cố gắng viết một hàm sử dụng lại chuyển đổi tiềm ẩn mà tôi có đối tượng A -> Object B khi chúng được bao bọc trong một Option theo cách tổng quát để các chuyển đổi Option [A] -> Option [B] cũng hoạt động.Scala: Chuyển đổi ẩn A-> B hoạt động cho Option [A] -> Option [B]

Những gì tôi đã đưa ra là:

implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_)) 

này hoạt động khi tôi gán Một số (..) đến một giá trị nhưng không khi tôi gán một lựa chọn val; xem đầu ra bảng điều khiển sau:

scala> trait T 
defined trait T 

scala> case class Foo(i: Int) extends T 
defined class Foo 

scala> case class Bar(i: Int) extends T 
defined class Bar 

scala> implicit def fromFooToBar(f: Foo):Bar = Bar(f.i) 
fromFooToBar: (f: Foo)Bar 

scala> implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i) 
fromBarToFoo: (b: Bar)Foo 

scala> implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_)) 
fromOptionToOption: [A, B](from: Option[A])(implicit conversion: (A) => B)Option[B] 

scala> val foo: Option[Foo] = Some(Bar(1)) 
foo: Option[Foo] = Some(Foo(1)) 
// THIS WORKS as expected 

scala> val fooOpt = Some(Foo(4)) 
fooOpt: Some[Foo] = Some(Foo(4)) 

scala> val barOpt2: Option[Bar] = fooOpt 
<console>:16: error: type mismatch; 
found : Some[Foo] 
required: Option[Bar] 
     val barOpt2: Option[Bar] = fooOpt 
           ^
//THIS FAILS. 

Tôi thực sự không thấy sự khác biệt giữa chuyển đổi đầu tiên và thứ hai. Bằng cách nào đó nó không gọi chuyển đổi ngầm trong cái sau. Tôi đoán nó có cái gì đó để làm với hệ thống loại, nhưng tôi không thể nhìn thấy như thế nào được nêu ra. Bất kỳ ý tưởng? -Albert (Tôi đang ở trên scala 2.9.1)

+2

Trong trường hợp đầu tiên, nó gọi 'fromFooToBar' một mình (nó quyết định vì nó cần' Option [Bar] ', nó thực sự gọi là' Some (x: Bar) ', và chuyển đổi thành đến đó); trong lần thứ hai nó phải gọi 'fromOptionToOption' và không. Điều này giải thích sự khác biệt, nhưng tôi không chắc chắn làm thế nào để giải quyết vấn đề. –

+0

Lưu ý: 'val barOpt: Option [Bar] = Một số (Foo (4))' hoạt động như mong đợi. –

+1

@Dan Burton - Chính xác; điều này là bởi vì nó được biến thành 'Some (fromBarToFoo (Foo (4)))'. –

Trả lời

13

Dưới đây là đầu mối:

scala> val fooOpt: Option[Bar] = Option(Foo(1)) 
fooOpt: Option[Bar] = Some(Bar(1)) 

Và khác:

scala> implicit def foobar(x: String): Int = augmentString(x).toInt 
foobar: (x: String)Int 

scala> val y: Option[String] = Option(1) 
y: Option[String] = Some(1) 

scala> val y: Option[Int] = Option("1") 
y: Option[Int] = Some(1) 

Có vẻ như một lỗi lẻ lẻ hợp pháp. Tôi sẽ mở một trường hợp thử nghiệm nhỏ hơn và mở một vấn đề (hoặc tìm kiếm một trong JIRA).

Là một sang một bên:

Bạn có thể sử dụng một số lý thuyết danh mục để xử lý nhiều loại khác nhau "Tùy chọn-ish".

package object fun { 
    trait Functor[Container[_]] { 
    def fmap[A,B](x: Container[A], f: A => B): Container[B] 
    } 
    object Functor { 
    implicit object optionFunctor extends Functor[Option] { 
     override def fmap[A,B](x: Option[A], f: A => B): Option[B] = x map f 
    } 
    // Note: With some CanBuildFrom magic, we can support Traversables here. 
    } 
    implicit def liftConversion[F[_], A, B](x: F[A])(implicit f: A => B, functor: Functor[F]): F[B] = 
    functor.fmap(x,f) 

} 

Đó là nâng cao hơn một chút, như bạn đang lập bản đồ một số FP lý thuyết loại vào vấn đề, nhưng đó là một giải pháp tổng quát hơn để nâng cuộc nói chuyện ngầm vào thùng chứa khi cần thiết. Lưu ý cách chuỗi quảng cáo bằng cách sử dụng một phương thức đối thoại tiềm ẩn mà mất một đối số ẩn hạn chế hơn.

CŨNG, điều này sẽ làm cho các ví dụ làm việc:

scala> val tmp = Option(Foo(1)) 
tmp: Option[Foo] = Some(Foo(1)) 

scala> val y: Option[Bar] = tmp 
y: Option[Bar] = Some(Bar(1)) 

Và chắc việc bạn sử dụng Some nguy hiểm hơn:

scala> val tmp = Some(Foo(1)) 
tmp: Some[Foo] = Some(Foo(1)) 

scala> val y: Option[Bar] = tmp 
<console>:25: error: could not find implicit value for parameter functor: fun.Functor[Some] 
     val y: Option[Bar] = tmp 
          ^

Đó là nói với bạn rằng sai là rất quan trọng, và tương tác với implicits . Đoán của tôi là bạn gặp phải một lỗi rất hiếm, có thể khó sửa lỗi có thể tránh được bằng các kỹ thuật khác.

+0

Cảm ơn! Vì vậy, bạn nói rằng giải pháp của tôi _should_ làm việc? Bạn cũng có thể chỉ cho tôi một số giải thích về lý thuyết thể loại (Tôi không giỏi ở FP) – Albert

+0

Tsk, tsk, tsk ... bạn nên làm một công việc tốt hơn để gỡ lỗi vấn đề! :-) –

+0

Tôi đã thử giải pháp lý thuyết danh mục và giải quyết nó. Vẫn không chắc chắn làm thế nào nó hoạt động mặc dù .. Cảm ơn một lần nữa – Albert

1

Thực ra đó là một vấn đề rất lạ. Tôi đã thử sử dụng loại khác hơn Option và hóa ra vấn đề là Option là biến thể trong thông số loại của nó. Này hoạt động tất cả:

case class A[B](value: B) // invariant in B 

case class X() 
case class Y() 

implicit def xtoy(x: X): Y = Y() 
implicit def ytox(x: Y): X = X() 
implicit def movea[U, V](from: A[U])(implicit view: U => V): A[V] = A[V](from.value) 

def test(a: A[Y]) = "ok" 
test(A(X())) // (1) 
val f = A(X()) 
test(f)  // (2) 

Nhưng nếu thay vào đó tôi xác định A như

case class A[+B](value: B) // covariant in B 

Vụ kiện (2) thất bại. Trường hợp (1) luôn thành công, bởi vì Scala đã chuyển đổi X thành Y trước khi gói nó trong một A.

Bây giờ chúng ta biết được vấn đề nguồn, bạn cần phải chờ đợi cho một loại guru để giải thích lý do tại sao này thực sự là một vấn đề ... Việc chuyển đổi vẫn còn hiệu lực, bạn sẽ thấy:

askForY(movea(f)) // succeeds, even with A[+B] 
+0

Tự hỏi liệu đây có phải là vấn đề tương tự không như trong câu hỏi dường như chưa được trả lời này: http://stackoverflow.com/questions/8472134/why-does-this-scala-implicit-conversion-fail-when-explicit-conversion-works –

13

Bạn có thể không biết điều đó, nhưng có một lá cờ cho điều đó: -Xlog-implicits. Và đây là những gì nó nói:

scala> val barOpt2: Option[Bar] = fooOpt 
fromOptionToOption is not a valid implicit value for Some[Foo] => Option[Bar] because: 
incompatible: (from: Option[Foo])(implicit conversion: Foo => B)Option[B] does not match expected type Some[Foo] => Option[Bar] 
<console>:16: error: type mismatch; 
found : Some[Foo] 
required: Option[Bar] 
     val barOpt2: Option[Bar] = fooOpt 
           ^

và có bạn đi - nó không biết những gì loại B phải. 0__ đã đề cập rằng vấn đề này không xảy ra với các bộ sưu tập bất biến, và điều đó có ý nghĩa gì đó. Trong các bộ sưu tập bất biến, B phải chính xác là Bar, trong khi đối với các tập hợp biến đổi, nó có thể là bất kỳ kiểu con nào của Bar.

Vì vậy, tại sao val foo: Option[Foo] = Some(Bar(1)) hoạt động? Vâng, có một lá cờ cho điều đó quá ... -Ytyper-debug. Tuy nhiên, không phải vì yếu đuối, với sự cực kỳ khốc liệt.

tôi lạch bạch qua dù sao, so sánh những gì xảy ra trong cả hai trường hợp, và câu trả lời là khá đơn giản ... nó không phải là Option đang được chuyển đổi trong trường hợp đó, nhưng Bar! Hãy nhớ rằng, bạn đã khai báo một chuyển đổi tiềm ẩn từ Bar => Foo, do đó, nó đang áp dụng chuyển đổi đó trước khi chuyển kết quả đến Some!

+0

Cảm ơn! Không biết về những lá cờ, chắc chắn sẽ giúp. Tôi bắt đầu 'làm' nó. Có cách nào để thực hiện chuyển đổi hoạt động với các loại biến thể không? (Tôi sẽ không biết làm thế nào để) – Albert

+0

Điều này là đúng, nhưng nó chỉ là giải thích nhiều hơn về cách mà trong đó scalac là không điền vào các loại theo cách duy nhất có thể hợp lý, đưa ra đầy đủ thông tin đầy đủ. Nó có thể, sau khi tất cả, chọn 'B' như vậy mà nó hoạt động, và nó biết chính xác những gì nó cần để nó hoạt động. Nó chỉ báo cáo cho bạn thay vì tự làm nó. –

+0

tùy chọn là '-Xlog-implicits' final' s' – pedrofurla

1

Nó không làm việc vì Scala Language Specification định nghĩa xem như sau:

thông số ngầm và các phương pháp cũng có thể xác định chuyển đổi tiềm ẩn gọi xem. Một quan điểm từ loại ST được xác định bởi một giá trị tiềm ẩn trong đó có loại chức năng S => T hoặc (=> S) => T hoặc bằng một phương pháp chuyển đổi thành một giá trị của loại hình đó .

fromOptionToOption không phù hợp với ba danh mục vì nó có tham số ẩn. Trình biên dịch dường như không tìm thấy trình chuyển đổi với cả đích và nguồn có loại chung.

Xác định chế độ xem từ Option[Foo] thành Option[Bar] hoạt động như mong đợi.

trait T 
case class Foo(i: Int) extends T 
case class Bar(i: Int) extends T 

object Main { 
    implicit def fromFooToBar(f: Foo):Bar = Bar(f.i) 
    implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i) 
    // implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = 
    // from.map(conversion(_)) 
    implicit def fromOptionFooToOptionBar(o: Option[Foo]): Option[Bar] = o map { foo => foo } 

    def test(): Option[Bar] = { 
    val fooOpt = Some(Foo(4)) 
    val barOpt2: Option[Bar] = fooOpt 

    barOpt2 
    } 
} 

println(Main.test) 

Chạy này in ra:

$ scala so.scala 
Some(Bar(4)) 

Tuy nhiên, tất cả là không bị mất. Nó không đẹp như chung Option đến Option, nhưng chúng ta có thể làm một cái gì đó giống như bất cứ thứ gì có thể biến thành Bar thành Option[Bar] theo chế độ xem bị ràng buộc.

trait T 
case class Foo(i: Int) extends T 
case class Bar(i: Int) extends T 

object Main { 
    implicit def fromFooToBar(f: Foo):Bar = Bar(f.i) 
    implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i) 
    implicit def fromOptionToOptionBar[A <% Bar](from: Option[A]): Option[Bar] = 
    from map { foo => foo } 

    def test(): Option[Bar] = { 
    val fooOpt = Some(Foo(4)) 
    val barOpt2: Option[Bar] = fooOpt 

    barOpt2 
    } 
} 

println(Main.test) 

Đây là một cách giải quyết có thể được sử dụng cho chung Option-Option nhưng đòi hỏi thêm .convert gọi:

trait T 
case class Foo(i: Int) extends T 
case class Bar(i: Int) extends T 

case class Converter[A](x: Option[A]) { 
    def convert[B](implicit ev: Function1[A, B]): Option[B] = x map { a: A => ev(a) } 
} 

object Main { 
    implicit def optionToConverter[A](x: Option[A]) = Converter(x) 
    implicit def fooToBar(x: Foo) = Bar(x.i) 

    def test(): Option[Bar] = { 
    val fooOpt = Some(Foo(4)) 
    val barOpt: Option[Bar] = fooOpt.convert 
    barOpt 
    } 
} 

println(Main.test) 
+1

Hm, chế độ xem AFAIK không bị giới hạn do không có thông số ngầm. Bằng chứng tốt nhất tại điểm này có thể là khung nhìn mà bạn đang sử dụng trong 'fromOptionToOptionBar [A <% Bar]' mà chính nó không là gì ngoài một tham số ngầm định (hoặc chính xác hơn, một chuyển đổi ngầm), chỉ với cú pháp. – fotNelton

+0

@fotNelton bạn nói đúng. Tôi đoán vấn đề thực sự là vì cả nguồn và đích đều có kiểu generic. –

+0

Mặc dù đã đọc gần như tất cả các câu trả lời và nhận xét tôi chưa nắm được lý do thực sự cho đến nay. Nhưng thời gian sẽ đến, tôi hy vọng :) – fotNelton

1

tôi cải thiện @jseureth answer và thêm hỗ trợ cho Traversable:

trait Mappable[A, B, C[_]] { 
    def apply(f: A => B): C[B] 
} 

package object app { 

    implicit class OptionMappable[A, B, C[X] <: Option[X]](option: C[A]) extends Mappable[A, B, Option] { 
    override def apply(f: A => B): Option[B] = option.map(f) 
    } 

    implicit class TraversableMappable[A, B, C[X] <: Traversable[X]](traversable: C[A]) 
    (implicit cbf: CanBuildFrom[C[A], B, C[B]]) extends Mappable[A, B, C] { 
    override def apply(f: A => B): C[B] = { 
     val builder = cbf(traversable) 
     builder.sizeHint(traversable) 
     builder ++= traversable.map(f) 
     builder.result() 
    } 
    } 

    implicit def liftConversion[C[_], A, B](x: C[A]) 
    (implicit f: A => B, m: C[A] => Mappable[A, B, C]): C[B] = m(x)(f) 

} 

Bây giờ bạn có thể chuyển đổi hoàn toàn các tùy chọn và chuyển đổi:

implicit def f(i: Int): String = s"$i" 

val a: Option[String] = Some(1) 
val b: Seq[String] = Seq(1, 2, 3) 
Các vấn đề liên quan