2011-12-30 3 views
3

Моя цель - создать признак, который может расширить класс case, который может обрабатывать каждый аргумент конструктора, а затем передавать их как аргументы методу класса case. Все аргументы конструктора будут одного и того же типа с разными параметрами типа, а метод будет принимать аргументы, соответствующие параметру типа каждого аргумента конструктора. Вы можете думать об этом как о предварительном процессоре аргументов конструктора. Например,Можно ли ссылаться на типы аргументов конструктора класса Scala?

case class Foo(input1:Input[Int], input2:Input[String]) extends MagicTrait { 
    def run(input1:Int, input2:String) { ... } 
} 

Возможно ли это? Возможно ли это так, что это не ужасно уродливо (например, все отражение)? Можно ли ссылаться на сопутствующий объект класса case таким образом, который является общим для классов case (например, функция, которая выводит результат Companion.unapply())?

+1

Учитывая значение типа Input [T] как получить соответствующее значение типа T? –

+0

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

+0

Хорошая параллель с моей ситуацией заключается в том, что каждый из аргументов является функцией, и я хочу запускать каждую функцию, а затем вызывать новую функцию с выходом каждой из функций. –

ответ

6

В качестве приемлемого решения можно исключить возможность перемещения функции предварительной обработки с экземпляров на связанный объект, основная трудность заключается в том, что вы хотите иметь возможность абстрагироваться от арности и типов (т. Е. Формы) вашего случая класса. Это возможно с помощью значений реализации и значений полиморфной функции HList от shapeless.

Первой некоторые предварительные,

import shapeless.HList._ 
import shapeless.Functions._ 
import shapeless.Poly._ 
import shapeless.TypeOperators._ 

// Implementation of your Input wrapper 
case class Input[T](value: T) 

// Value extractor as a shapeless polymorphic function value 
object value extends (Input ~> Id) { 
    def default[T](i : Input[T]) = i.value 
} 

Теперь мы можем определить препроцессор базового класса, который обеспечивает способ применения, который принимает HList из Input типов, карты полиморфного функцию value через него (то есть. Выполняет предварительную обработку) и затем передает результирующий HList из не- Input типов предоставленному случай, конструктор класса (который приведен в hlisted форме, смотри ниже),

// Pre-processer base class 
abstract class Preprocessor[In <: HList, Out <: HList, R](ctor : Out => R) 
    (implicit mapper : MapperAux[value.type, In, Out]) { 
    def apply(in : In) = ctor(in map value) 
    } 

Теперь определим класс случай с типами компонентов пост-обработки,

case class Foo(input1 : Int, input2 : String) 

и добавить одну строку шаблонный,

object FooBuilder extends Preprocessor((Foo.apply _).hlisted) 

(здесь метод объекта компаньон завод Foo предусмотрен как Preprocessor аргумент конструктора в HListed форме, как требуется выше.)

Теперь мы можем построить экземпляры Foo, используя FooBuilder.

val foo = FooBuilder(Input(23) :: Input("foo") :: HNil) 

К сожалению, это не так (в настоящее время) можно объединить FooBuilder объект с объектом Foo компаньона: если вы пытаетесь иметь Foo компаньона продлить Preprocessor вы обнаружите, что метод Foo завода не доступен который должен быть передан как аргумент конструктора Preprocessor.

Чтобы показать, что это решение действительно абстрагируясь над типом и арностью, вот как мы могли бы добавить второй по-разному формы класса случай,

case class Bar(input1 : Int, input2 : String, input3 : Boolean) 

object BarBuilder extends Preprocessor((Bar.apply _).hlisted) 

val bar = BarBuilder(Input(23) :: Input("foo") :: Input(true) :: HNil) 
+0

Это потрясающий ответ! Огромное спасибо. Он напрямую решает мою проблему, является очень полным, и я многому научился у него и просмотрел вашу бесформенную библиотеку. –

2
case class Input[T](value: T) 

trait MagicTrait[T,U] { 
    val input1: Input[T] 
    val input2: Input[U] 
    def run: Unit 
} 

case class Foo(input1: Input[Int], input2: Input[String]) 
    extends MagicTrait[Int, String] { 
    def run = println(input1.value * 2 + input2.value.toUpperCase) 
} 

scala> val m: MagicTrait[_,_] = Foo(Input(3), Input("hi")) 
m: MagicTrait[_, _] = Foo(Input(3),Input(hi)) 

scala> m.run 
6HI 

редактировать:

Если вы хотите, чтобы найти типы параметров класса вы можете использовать тот факт, что тематические классы продлить Product:

scala> Foo(2, "hi").productIterator.map(_.asInstanceOf[AnyRef].getClass).toList 
res13: List[java.lang.Class[_]] = 
     List(class java.lang.Integer, class java.lang.String) 

Но это использует отражение вы хотели избежать. Вот почему мы используем параметризацию.

Если вы хотите вернуть свой сопутствующий объект, я не уверен, что вы можете сделать это полезным и безопасным способом в контексте классов case, поскольку объекты-компаньоны не расширяют интерфейс, который указывает их экстрактор методы. Возможно, вы сможете сделать что-то со структурными типами, но, вероятно, есть лучший способ подойти к любой проблеме, которую вы пытаетесь решить.

+0

Моя надежда состояла в том, чтобы ссылаться на типы аргументов класса case без необходимости параметризовать признак типов аргументов класса case. И если бы я должен был следовать этому пути, мне нужно создать черту для каждой из них - например, MagicTrait1 (один аргумент), MagicTrait2 (два аргумента), MagicTrait3 и т. Д. Но, может быть, нет лучшего решения? –

+0

Что случилось с конструктором типа ввода [_] здесь? –

+0

@MilesSabin Я добавил это к своему ответу, но это ничего не меняет, кроме грязных вод; точка, в которой вы можете реализовать параметризованный признак, используя аргументы конструктора класса case, одинакова. –

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