2013-05-12 2 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") 

Я хотел бы сделать это более DSL-как, позволяя Method экземпляр быть преобразованы в Builder например, Однако, когда я добавляю попытаться неявно включают в себя 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 вызывается явно, параметр builder может быть найден неявно. Также, если я удалю общие аргументы (и тип безопасности), код работает так, как ожидалось.

Является ли это ограничением в неявных преобразованиях 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. Новый конструктор создается с указанным URI с помощью url
  2. Это затем повторно в сторону закрытия, как implicit b =>
  3. assert метод доступен только потому, что ури и метод указаны
  4. / прилагается к текущему ури, это доступно только потому, что у конструктора указан 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

Вы можете дать это посмотреть и увидеть, если он проливает свет на то, что происходит с вашей неявной функцией, которая имеет неявные параметры , http://stackoverflow.com/questions/5080406/implicit-parameters-in-implicit-conversions – cmbaxter

+1

Я бы пересмотрел дизайн. Вы дублируете информацию странным образом - с одной стороны, у вас есть параметр конструктора типа, указывающий, имеет ли компоновщик метод или URL-адрес во время компиляции, в то же время у вас есть параметры, которые могут быть разрешены только во время выполнения. Вопрос в том, чего вы хотите достичь (где должны проверяться типы)? Если вы переходите к параметрам типа, создайте подклассы, которые несут метод и URL (а не параметры). Наконец, вы хотите поднять метод к строителю. Нет необходимости проверять существующий тип 'HasUri', он всегда должен быть FALSE? –

+0

@cmbaxter, спасибо, я постараюсь скомпилировать мой код с этими параметрами отладки, чтобы увидеть, проливает ли он какой-либо свет. – iain

ответ

1

Этот код теперь работает как есть в Scala 2.11, однако он не работает в Scala 2.10 (который я использовал, чтобы написать этот исходный код).

Я искал причину, почему это может быть так, и вы можете увидеть эту ошибку только в jira scala-lang.

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

Я попробовал несколько способов решить эту проблему в Scala 2.10, но не мог.Среди них @ предложения Edmondo1984 «s и ограничение параметров HasMethod и HasUri, как показано ниже:

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) 

Если я не ошибаюсь, экзистенциальный тип рассматривается как Ничто в этом случае.Ничто не является подклассом каждого возможного класса Scala, так что ваш метод фактически становится:

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

Scala будет искать в текущей области, чтобы найти подкласс Builder [Ничего, HasUri], чтобы обеспечить ваш метод и нет класса, который может соответствовать требуемому типу кроме Builder [Nothing, HasUri], потому что ваш класс строителя инвариантно, т.е. Builder[A,B]<:<Builder[C,D] тогда и только тогда A=:=C & B=:=D

у вас есть поэтому два варианта:

  • добавить параметр подписи, toM еню [HasUri] становится toMethod [A, HasUri]
  • Exploit Scala Правильная реализация типа дисперсиями

Поскольку вы хотите обеспечить, что ваш строитель [A, HasUri] является подклассом Builder [Ничего, HasUri] и

Nothing <:< A for any A 

вы хотите обеспечить, что Builder[A,HasUri] <:< Builder[B,HasUri] тогда и только тогда B<:<A т.е. Builder является controvariant в качестве первого параметра типа. Вы поддерживаете controvariance, поставив - Simbol перед типа:

Builder[-HasMethod, HasUri] является controvariant в HasMethod и инвариантно в HasUri


Заключение

Тип системы являются мощными, но это не является обязательным использовать сложные шаблоны даже для простых задач:

  • HasUri не выведен из m, так как i т является параметром типа из метода toMethod
  • HasMethod не выведенного, потому что вы удалите его с помощью _

Какой смысл иметь неявный параметр с двумя дженериков аргументами, если аргументы не участвуют в ваше разрешение? Я бы просто написать:

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

Когда вы в конечном итоге с такого рода ситуациями, как кто-то уже сказал, это потому, что ваш дизайн является неправильным для этой проблемы. Можете ли вы объяснить, почему строитель должен быть скрытым в методе?

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

Спасибо за подробный ответ. Сегодня вечером я попробую ваши предложения. Относительно: В чем смысл наличия неявного параметра с двумя аргументами дженериков, если аргументы не участвуют в вашем разрешении? Неявный параметр используется для получения текущего конструктора по умолчанию в DSL. Генерики генерируют состояние строителя, 'build()' доступно только в 'Builder [TRUE, TRUE]'. «ToMethod» создает новый построитель, добавляя метод к текущему по умолчанию. Это значение по умолчанию может иметь или не иметь метод и набор URL, новый будет иметь набор методов. – iain

+0

Тогда хорошая идея - иметь объект BuildingScope, неявный с разрешением по умолчанию, который будет разрешен – Edmondo1984