2013-05-12 46 views
4

我正在爲簡單的休息請求使用Scala類型安全的構建器模式。這可以作爲流暢的API。隱式轉換不適用於類型安全的構建器模式

sealed abstract class Method(name: String) 

case object GET extends Method("GET") 
case object POST extends Method("POST") 

abstract class TRUE 
abstract class FALSE 

case class Builder[HasMethod, HasUri](
    method: Option[Method], 
    uri: Option[String]) { 

    def withMethod(method: Method): Builder[TRUE, HasUri] = copy(method = Some(method)) 
    def withUri(uri: String): Builder[HasMethod, TRUE] = copy(uri = Some(uri)) 
} 

implicit val init: Builder[FALSE, FALSE] = Builder[FALSE, FALSE](None, None) 

//Fluent examples 
val b1: Builder[TRUE, FALSE] = init.withMethod(GET) 
val b2: Builder[TRUE, TRUE] = init.withMethod(GET).withUri("bar") 

我想通過允許Method實例轉換成Builder實例,以使這更DSL的喜歡,但是當我加試隱含地包括init建設者的隱式轉換和類型參數的組合混淆編譯器。

implicit def toMethod[HasUri](m: Method) 
    (implicit builder: Builder[_, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m) 

// ** ERROR **: could not find implicit value for parameter builder: 
//    Builder[_, HasUri] 
val b3: Builder[TRUE, TRUE] = GET withUri "foo" 

// However the implicit parameter is discovered fine when function is called directly 
val b4: Builder[TRUE, FALSE] = toMethod(GET) 
val b5: Builder[TRUE, TRUE] = toMethod(GET) withUri "foo" 

除b3以外的所有行編譯。當明確調用toMethod函數時,可以隱式找到構建器參數。此外,如果我刪除通用參數(和類型安全)代碼按預期工作。

這是scala隱式轉換的限制嗎?或者我錯過了正確的語法來實現這一目標?

我想隱式發現初始構建器實例,以使用戶能夠爲其某些構建器的字段提供其默認值的初始構建器。

更新

我留下了一些代碼,以保持例子簡單,因爲它只是我正在修隱式轉換。

類型安全的建設者模式概括得非常好位置:http://blog.rafaelferreira.net/2008/07/type-safe-builder-pattern-in-scala.html

之後你只能調用build方法一旦Builder有一個方法和URI。

我想將構建器作爲隱式參數發現的原因是在DSL中支持以下情況。

url("http://api.service.org/person") apply { implicit b => 
    GET assert(Ok and ValidJson) 
    GET/"john.doe" assert(NotFound) 
    POST body johnDoeData assert(Ok) 
    GET/"john.doe" assert(Ok and bodyIs(johnDoeData)) 
} 

在這些情況下

  1. 新的構建是通過url
  2. 這具有指定URI創建,然後在側重用封口implicit b =>
  3. assert方法只適用,因爲已經指定了uri和方法
  4. /附加到當前uri,這是隻有建造者有指定的uri纔可用。

已指定方法和URI的另一個例子

GET url("http://api.service.org/secure/person") apply { implicit b => 
    auth basic("harry", "password") assert(Ok and ValidJson) 
    auth basic("sally", "password") assert(PermissionDenied) 
} 
+1

你可能想給這個看看,看看它是怎麼回事與具有隱含的PARAM你的隱函數揭示光。 http://stackoverflow.com/questions/5080406/implicit-parameters-in-implicit-conversions – cmbaxter 2013-05-12 22:30:59

+1

我會重新考慮設計。您以一種奇怪的方式複製信息 - 一方面,您有一個類型構造函數參數,指示構建器在編譯時是否有方法或url,同時您擁有隻能在運行時解決的選項。問題是,你想達到什麼樣的目標(應該在哪裏檢查類型)?如果您使用類型參數,則創建攜帶該方法和url的子類(而不是選項)。最後,你想提升一個方法建設者。沒有必要檢查現有的'HasUri'類型,它應該總是'FALSE'? – 2013-05-12 22:50:36

+0

@cmbaxter,謝謝,我會嘗試編譯我的代碼與這些調試選項,看看它是否擺脫任何光。 – iain 2013-05-13 08:57:44

回答

1

此代碼現在作爲,在斯卡拉2.11,但它並沒有在斯卡拉2.10(我是用寫這個原代碼)工作。

我正在尋找一個原因,爲什麼這可能是這種情況,只能看到在斯卡拉朗吉拉找到這個bug。

https://issues.scala-lang.org/browse/SI-3346

我已經嘗試了多種方法在斯卡拉2.10來解決這個,但不能。這些包括@ Edmondo1984的建議和限制HasMethodHasUri參數如下:

case object GET extends Method("GET") 
case object POST extends Method("POST") 

sealed trait TBool 
trait TTrue extends TBool 
trait TFalse extends TBool 

case class Builder[HasMethod <: TBool, HasUri <: TBool](method: Option[Method], 
                 uri: Option[String]) { 

    def withMethod(method: Method): Builder[TTrue, HasUri] = copy(method = Some(method)) 
    def withUri(uri: String): Builder[HasMethod, TTrue] = copy(uri = Some(uri)) 
} 

object Builder { 
    implicit val init: Builder[TFalse, TFalse] = Builder[TFalse, TFalse](None, None) 

    // Example build method 
    implicit class CanExecute(builder: Builder[TTrue, TTrue]) { 
    def execute(): String = s"Build(${builder.method} ${builder.uri}" 
    } 
} 


//Fluent examples 
val b1: Builder[TTrue, TFalse] = init.withMethod(GET) 
val b2: Builder[TTrue, TTrue] = init.withMethod(GET).withUri("bar") 


implicit def toMethod[HasUri <: TBool](m: Method) 
             (implicit builder: Builder[_, HasUri]): Builder[TTrue, HasUri] = builder.withMethod(m) 

// ** ERROR **: could not find implicit value for parameter builder: 
//    Builder[_, HasUri] 
// ** BUT ** Works in Scala 2.11 
val b3: Builder[TTrue, TTrue] = GET withUri "foo" 

GET withUri "foo" execute() 
1

我有你的隱式解析的問題不是來自於Scala的類型系統任何限制的感覺,但它依賴於生存型你這裏指定:

implicit def toMethod[HasUri](m: Method) 
    (implicit builder: Builder[_, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m) 

如果我沒有錯,在這種情況下,存在類型被視爲無。沒有什麼是每一個可能的斯卡拉類的子類,讓你的方法實際上就變成了:

implicit def toMethod[HasUri](m: Method) 
    (implicit builder: Builder[Nothing, HasUri]): Builder[TRUE, HasUri] = builder.withMethod(m) 

然後斯卡拉看起來在當前範圍內找到生成器[沒什麼,HasUri]的子類提供給您的方法和沒有課,可以匹配除了生成器[沒什麼,HasUri]所需要的類型,因爲你的器類是不變的,即Builder[A,B]<:<Builder[C,D] IFF A=:=C & B=:=D

因此,你有兩個選擇:

  • 添加參數簽名,toM ethod [HasUri]成爲toMethod [A,HasUri]
  • 開拓斯卡拉正確執行型的方差

既然要強制你的生成器[A,HasUri]是生成器[沒什麼,HasUri]的子類和

Nothing <:< A for any A 

要強制執行Builder[A,HasUri] <:< Builder[B,HasUri] IFF B<:<A即Builder是在第一類參數controvariant。你可以通過把一個執行controvariance - 辛博爾在型前:

Builder[-HasMethod, HasUri]是HasMethod controvariant和不變的HasUri


結論

類型系統是強大的,但它不是強制性的使用複雜的模式,即使是簡單的任務:

  • HasUri不是從m推斷,因爲我t是從方法toMethod
  • HasMethod不推斷類型參數,因爲您在使用擦除它_

是什麼,如果參數未在參與具有帶有兩個泛型參數的隱含參數的點你的決議?我就簡單的寫:

case class DefaultBuilder(m:Method) extends Builder[True,HasUri] 

當你結束了這些種情況,因爲已經有人說,那是因爲你的設計是錯誤的問題。你能解釋爲什麼生成器必須隱含在toMethod中嗎?

implicit def toMethod(m:Method) = DefaultBuilder(m) 
+0

感謝您的詳細解答。我會在今晚嘗試你的建議。 關於: 如果參數不包含在您的分辨率中,那麼使用含兩個泛型參數的隱式參數有什麼意義? 隱式參數用於獲取DSL中當前的默認構建器。泛型捕獲構建器的狀態,'build()'僅在'Builder [TRUE,TRUE]'上可用。 'toMethod'通過將方法添加到當前的默認值來創建新的構建器。這個默認值可能會或可能沒有設置方法和url,新的會設置方法。 – iain 2013-05-21 15:57:40

+0

然後好的想法是讓一個具有defaultBuilder的BuildingScope隱式地被解析 – Edmondo1984 2013-05-21 16:31:52