2012-05-23 2 views
4

Я создаю простую схему инъекции зависимостей для инъекции конструктора в Scala. Идея заключается в том, что объекты, которые DI'd помещают свои требуемые сервисы в свой конструктор, как обычные параметры, и реализуют класс типов, который определяет, какие из их аргументов берутся из контейнера и которые передаются пользователем при создании экземпляра.Имитировать частичный вывод параметра типа с implicits?

Таким образом, это должно выглядеть примерно так:

trait Container { 
    private singletons: Map[Class, AnyRef] 
    def getSingleton[T: Manifest] = 
    singletons(implicitly[Manifest[T]].erasure).asInstanceOf[T] 
    ... methods for adding singletons, etc ... 
} 

class Foo(arg: String, svc: FooService) { 
    ... 
} 

trait Constructor[T] { ??? }  

object FooConstructor extends Constructor[Foo] { 
    def construct(arg: String)(implicit container: Container) = 
    new Foo(arg, container.getSingleton[FooService]) 
} 

Сейчас в основном я хотел бы иметь возможность иметь метод, называемый construct, который я могу назвать, как construct[Foo]("asd") и получить новый экземпляр Foo с "asd" переданный конструктору, и FooService, полученный из локального контейнера и переданный конструктору. Идея состоит в том, что он должен захватить экземпляр класса Constructor для Foo и в виде типа, узнать количество и типы аргументов, которые он должен иметь. Кроме того, и это сложная часть, я не хочу записывать типы аргументов - просто объект, который нужно построить.

Я попробовал несколько вещей:

trait Constructor1[T, A] { 
    def construct(arg: A): T 
} 

trait Constructor2[T, A1, A2] { 
    def construct(arg1: A1, arg2: A2): T 
} 

def construct[T, A](arg1: A): T = implicitly[Constructor1[T, A]].construct(arg1) 

... 

Этот подход не работает, хотя, потому что кажется, что для того, чтобы «вызвать» экземпляр класса Constructor типа, мы должны записать типы аргументы, которые много неприятных шаблонный:

construct[Foo, String]("asd") // yuck! 

есть ли способ, чтобы использовать классы типов (или что-нибудь еще), чтобы сортировать частично вывести параметры типа? У нас есть типы параметров конструктора для Foo, определенные в определении экземпляра Constructor, поэтому, если мы можем вызвать экземпляр, мы должны просто вызвать construct и получить правильные типы аргументов. Проблема заключается в получении этого экземпляра без указания аргументов типа конструктора. Я играл с кучей различных идей для этого, и я чувствую, что с силой и сумкой трюков Scala там просто имеет, чтобы быть способ, который я могу написать construct[Foo]("asd") и иметь список аргументов, который будет безопасным. Есть идеи?

ОБНОВЛЕНИЕ: Отличный ответ Майлза Сабина + небольшая модификация, вот метод, который требует только одного параметра типа и работает для всех длин списка аргументов. Это довольно простой способ безболезненного телеграфировать зависимостей, без стоимости отражения:

trait Constructor1[T, A] { def construct(arg1: A)(implicit c: Container): T } 
trait Constructor2[T, A, B] { def construct(arg1: A, arg2: B)(implicit c: Container): T } 

implicit object FooConstructor extends Constructor1[Foo, String] { 
    def construct(arg1: String)(implicit c: Container) = 
    new Foo(arg1, c.getSingleton[FooService]) 
} 

implicit object BarConstructor extends Constructor2[Bar, String, Int] { 
    def construct(arg1: String, arg2: Int)(implicit c: Container) = 
    new Bar(arg1, arg2, c.getSingleton[FooService]) 
} 

class Construct[T] { 
    def apply[A](arg1: A)(implicit ctor: Constructor1[T, A], container: Container) = 
    ctor.construct(arg1) 
    def apply[A, B](arg1: A, arg2: B)(implicit ctor: Constructor2[T, A, B], container: Container) = 
    ctor.construct(arg1, arg2) 
} 

def construct[T] = new Construct[T] 

construct[Foo]("asd") 
construct[Bar]("asd", 123) 

ответ

8

Тип вывода параметра в Scala является все или ничего дело: если вы явно поставить какой-либо из аргументов типа для параметра типа блок, то вы должны предоставить их всем. Следовательно, если вы хотите предоставить только некоторые из множества аргументов типа, вы должны организовать их принадлежность к отдельным блокам параметров типа.

Способ сделать это в этом случае состоит в том, чтобы разбить метод construct на два этапа: первый, который принимает явный аргумент типа и возвращает функциональное значение; и второй, который применяет функционально-подобное значение к аргументам, для которых вы хотите, чтобы типы были выведены.

Вот как это может пойти,

// Function-like type 
class Construct1[T] { 
    def apply[A](arg1: A)(implicit ctor : Constructor1[T, A]): T = 
    ctor.construct(arg1) 
} 

def construct[T] = new Construct1[T] 

Результат вызова construct[Foo] является значение типа Construct1[Foo].Это имеет метод apply с параметром типа, который может быть выведен, и неявный параметр, тип которого определяется как T, так и A. Вызов вы хотите сделать сейчас выглядит,

construct[Foo].apply("asd") // T explicit, A inferred as String 

семантические правила Обсахаривания Scala вокруг apply применяется здесь, который означает, что это может быть переписано в виде,

construct[Foo]("asd") 

который точно результат, который вы хотите.

+0

Спасибо, что ответили, я надеялся, что вы взглянете на него ... Извините, вопрос был настолько длинным! К сожалению, это еще не совсем решает проблему, потому что я не могу сделать перегрузку для работы разных длин списков. Используя этот подход, я должен был бы сказать 'construct1 [Foo] (« asd »)', 'construct2 [Bar] (« asd », 123)» и т. Д. Я попытался добавить неявный параметр доказательства для дифференциации перегрузок из 'construct' (' construct [T] (неявный ev: Constructor [T, _, _]) и т. д.), но он, похоже, не работает ... – lvilnis

+0

Я решил это! Хитрость заключается в том, чтобы не иметь объект Construct1 [T] ', а просто объект' Construct [T] ', который имеет все методы« apply », по одному для каждой длины списка аргументов. Я уточню свой вопрос. Спасибо за вашу помощь! – lvilnis

0
trait Construct[T, A] { 
def apply(arg: A): T 
} 

class Constructor[T]{ 
    def apply[A](arg : A)(implicit construct : Construct) = construct(arg) 
} 

object Constructor { 
def apply[T] = new Constructor[T] 
} 

Constructor[T]("arg") 
+1

Это по существу то же самое, что и принятый ответ (который на два года старше), за исключением того, что он даже не компилируется и не имеет объяснений. –

Смежные вопросы