Переменное количество типов по признаку - PullRequest
5 голосов
/ 09 ноября 2011

Я создаю простую черту кеша (для легкого кеширования своих функций):

trait Cache[A,B] {
  def _calc(v:A):B
  private var cache = Map[A,B]()
  def calc(v:A):B = {
    cache.get(v) match {
      case Some(x) => x
      case None => 
        val x = _calc(v)
        cache += (v -> x)
        x
    }
  }
}

Использование:

object Sol extends Cache[Int,Int] {
  def _calc(v:Int):Int = { /* do something here */ }
}
Sol.calc(5)

Это работает правильно, но проблема возникает, когда янеобходимо кэшировать функции с большим количеством аргументов - поэтому мне нужно разработать черты Cache2, Cache3, весь код вставки копий из первой черты.

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

Есть ли способ сделать это более широко и избежать нарушения DRY принципа ?

Ответы [ 4 ]

3 голосов
/ 09 ноября 2011

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

Этот подход может показаться уродливым, но он используется даже в библиотеке Scala для определения исходного кода TupleN, ProductN и FunctionN (где N - это int меньше 21).

1 голос
/ 09 ноября 2011

Я не совсем уверен, почему вы не захотели преобразовывать функцию с несколькими аргументами в функцию, которая принимает кортеж, поскольку вы все равно предположительно собираетесь кэшировать аргументы как кортеж.Преобразование может быть полностью внутренним для реализации запоминания, поэтому оно не влияет на вызывающих.Это именно то, что делает FunctionDecorator :

trait FunctionDecorator {
   final def apply[T, R, F](f: F)(implicit e: Tupler[F, T => R]): F = 
      e.untupled(decorate(e.tupled(f)))

   protected def decorate[T, R](f: T => R): T => R
}

Когда вы применяете декоратор (например, Memoize) к функции, он переписывает функцию, декорирует функцию с кортежем и освобождаетоформленная функция, так что ее можно вызывать так же, как и исходную функцию.

Конечно, это сдвигает вашу проблему СУХОЙ к определению Tuplers для всех типов функций.Я бы взял предложение парадигмы и использовал бы скрипт для определения Туплеров.Кортежи гораздо полезнее, чем куча типов CacheN.

1 голос
/ 09 ноября 2011

Вы можете, по крайней мере, автоматически связать свои функции так, чтобы вы могли определить действительную функцию (например, add), принимающую обычный список параметров:

object Add extends Cache[(Int, Int),Int]{
    def add (a:Int,b:Int):Int = a+b
    def _calc(t:(Int, Int)) = add _ tupled t
}

Вызовите его через Add.calc(3, 5)

Редактировать: Или использовать этот способ для реализации Cache2 ... CacheN без повторения всего кода Cache.

trait Cache2[A, B, C] extends Cache [(A,B), C] {
    def _calc2(a: A, b: B):C
    def _calc(t:(A,B)) = _calc2 _ tupled t
}

И снова

object Add2 extends Cache2[Int,Int,Int] {
    def _calc2(a:Int, b:Int) = a+b
}

и Add2.calc(3, 5)

1 голос
/ 09 ноября 2011

Возможное решение - использовать varargs для функций calc:

trait Cache[A,B] {
  def _calc(v:A*):B
  private var cache = Map[Seq[A],B]()
  def calc(v:A*):B = {
    cache.get(v.toList) match {
      case Some(x) => x
      case None => 
      val x = _calc(v:_*)
      cache += (v -> x)
      x
      }
   }
 }

object Sol1 extends Cache[Int,Int] {
  def _calc(v:Int*):Int = {
    require(v.length<2 && v.length>0, "use Sol2 for two-argument functions")
    v.head
  }
}
object Sol2 extends Cache[Int,(Int,Int)] {
  def _calc(v:Int*):(Int,Int) = {
    require(v.length<3 && v.length>1, "use Sol1 for single-argument functions")
    (v.head,v.last)
  }
}

Но я верю, что есть более чистая и умная работа вокруг.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...