Scala: memoize функция независимо от того, сколько аргументов выполняет функция?
Я хочу написать функцию memoize в scala, которая может быть применена к любому объекту функции независимо от того, что это за объект функции. Я хочу сделать это таким образом, чтобы я мог использовать единую реализацию memoize. Я гибко отношусь к синтаксису, но в идеале memoize появляется где-то очень близко к объявлению функции, а не после функции. я также хотел бы избежать первого объявления исходной функции, а затем второго объявления для memoized версии.
поэтому может быть и такой идеальный синтаксис:
def slowFunction(<some args left intentionally vague>) = memoize {
// the original implementation of slow function
}
и даже это было бы приемлемо:
def slowFUnction = memoize { <some args left intentionally vague> => {
// the original implementation of slow function
}}
Я видел способы сделать это, когда memoize необходимо переопределить для каждой функции arity, но я хочу избежать этого подхода. причина в том, что мне нужно будет реализовать десятки функций, подобных memoize (т.е. другим декораторам), и это слишком много, чтобы попросить скопировать каждый из них для каждой функции arity.
один способ сделать memoize, что требует повторения memoize-объявлений (так что это нехорошо) находится на Какой тип использовать для хранения измененной таблицы данных в памяти в Scala?.
Ответы
Ответ 1
Вы можете использовать подход типа-типа для решения проблемы arity. Вам все равно придется иметь дело с каждой функцией, которую вы хотите поддержать, но не для каждой комбинации arity/decorator:
/**
* A type class that can tuple and untuple function types.
* @param [U] an untupled function type
* @param [T] a tupled function type
*/
sealed class Tupler[U, T](val tupled: U => T,
val untupled: T => U)
object Tupler {
implicit def function0[R]: Tupler[() => R, Unit => R] =
new Tupler((f: () => R) => (_: Unit) => f(),
(f: Unit => R) => () => f(()))
implicit def function1[T, R]: Tupler[T => R, T => R] =
new Tupler(identity, identity)
implicit def function2[T1, T2, R]: Tupler[(T1, T2) => R, ((T1, T2)) => R] =
new Tupler(_.tupled, Function.untupled[T1, T2, R])
// ... more tuplers
}
Затем вы можете реализовать декоратор следующим образом:
/**
* A memoized unary function.
*
* @param f A unary function to memoize
* @param [T] the argument type
* @param [R] the return type
*/
class Memoize1[-T, +R](f: T => R) extends (T => R) {
// memoization implementation
}
object Memoize {
/**
* Memoize a function.
*
* @param f the function to memoize
*/
def memoize[T, R, F](f: F)(implicit e: Tupler[F, T => R]): F =
e.untupled(new Memoize1(e.tupled(f)))
}
Ваш "идеальный" синтаксис не будет работать, потому что компилятор предположил бы, что блок, переданный в memoize
, является лексическим замыканием 0-аргументов. Однако вы можете использовать последний синтаксис:
// edit: this was originally (and incorrectly) a def
lazy val slowFn = memoize { (n: Int) =>
// compute the prime decomposition of n
}
Edit:
Чтобы устранить множество шаблонов для определения новых декораторов, вы можете создать признак:
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 как
object Memoize extends FunctionDecorator {
/**
* Memoize a function.
*
* @param f the function to memoize
*/
protected def decorate[T, R](f: T => R) = new Memoize1(f)
}
Вместо вызова метода memoize
объекта Memoize вы непосредственно применяете объект Memoize:
// edit: this was originally (and incorrectly) a def
lazy val slowFn = Memoize(primeDecomposition _)
или
lazy val slowFn = Memoize { (n: Int) =>
// compute the prime decomposition of n
}
Ответ 2
Библиотека
Использовать Scalaz scalaz.Memo
Руководство
Ниже приведено решение, подобное ответу Aaron Novstrup и этот блог, за исключением некоторых исправлений/улучшений, краткости и облегчения для людей копирования и вставки необходимо:)
import scala.Predef._
class Memoized[-T, +R](f: T => R) extends (T => R) {
import scala.collection.mutable
private[this] val vals = mutable.Map.empty[T, R]
def apply(x: T): R = vals.getOrElse(x, {
val y = f(x)
vals += ((x, y))
y
})
}
// TODO Use macros
// See si9n.com/treehugger/
// http://stackoverflow.com/questions/11400705/code-generation-with-scala
object Tupler {
implicit def t0t[R]: (() => R) => (Unit) => R = (f: () => R) => (_: Unit) => f()
implicit def t1t[T, R]: ((T) => R) => (T) => R = identity
implicit def t2t[T1, T2, R]: ((T1, T2) => R) => ((T1, T2)) => R = (_: (T1, T2) => R).tupled
implicit def t3t[T1, T2, T3, R]: ((T1, T2, T3) => R) => ((T1, T2, T3)) => R = (_: (T1, T2, T3) => R).tupled
implicit def t0u[R]: ((Unit) => R) => () => R = (f: Unit => R) => () => f(())
implicit def t1u[T, R]: ((T) => R) => (T) => R = identity
implicit def t2u[T1, T2, R]: (((T1, T2)) => R) => ((T1, T2) => R) = Function.untupled[T1, T2, R]
implicit def t3u[T1, T2, T3, R]: (((T1, T2, T3)) => R) => ((T1, T2, T3) => R) = Function.untupled[T1, T2, T3, R]
}
object Memoize {
final def apply[T, R, F](f: F)(implicit tupled: F => (T => R), untupled: (T => R) => F): F =
untupled(new Memoized(tupled(f)))
//I haven't yet made the implicit tupling magic for this yet
def recursive[T, R](f: (T, T => R) => R) = {
var yf: T => R = null
yf = Memoize(f(_, yf))
yf
}
}
object ExampleMemoize extends App {
val facMemoizable: (BigInt, BigInt => BigInt) => BigInt = (n: BigInt, f: BigInt => BigInt) => {
if (n == 0) 1
else n * f(n - 1)
}
val facMemoized = Memoize1.recursive(facMemoizable)
override def main(args: Array[String]) {
def myMethod(s: Int, i: Int, d: Double): Double = {
println("myMethod ran")
s + i + d
}
val myMethodMemoizedFunction: (Int, Int, Double) => Double = Memoize(myMethod _)
def myMethodMemoized(s: Int, i: Int, d: Double): Double = myMethodMemoizedFunction(s, i, d)
println("myMemoizedMethod(10, 5, 2.2) = " + myMethodMemoized(10, 5, 2.2))
println("myMemoizedMethod(10, 5, 2.2) = " + myMethodMemoized(10, 5, 2.2))
println("myMemoizedMethod(5, 5, 2.2) = " + myMethodMemoized(5, 5, 2.2))
println("myMemoizedMethod(5, 5, 2.2) = " + myMethodMemoized(5, 5, 2.2))
val myFunctionMemoized: (Int, Int, Double) => Double = Memoize((s: Int, i: Int, d: Double) => {
println("myFunction ran")
s * i + d + 3
})
println("myFunctionMemoized(10, 5, 2.2) = " + myFunctionMemoized(10, 5, 2.2))
println("myFunctionMemoized(10, 5, 2.2) = " + myFunctionMemoized(10, 5, 2.2))
println("myFunctionMemoized(7, 6, 3.2) = " + myFunctionMemoized(7, 6, 3.2))
println("myFunctionMemoized(7, 6, 3.2) = " + myFunctionMemoized(7, 6, 3.2))
}
}
Когда вы запустите ExampleMemoize, вы получите:
myMethod ran
myMemoizedMethod(10, 5, 2.2) = 17.2
myMemoizedMethod(10, 5, 2.2) = 17.2
myMethod ran
myMemoizedMethod(5, 5, 2.2) = 12.2
myMemoizedMethod(5, 5, 2.2) = 12.2
myFunction ran
myFunctionMemoized(10, 5, 2.2) = 55.2
myFunctionMemoized(10, 5, 2.2) = 55.2
myFunction ran
myFunctionMemoized(7, 6, 3.2) = 48.2
myFunctionMemoized(7, 6, 3.2) = 48.2
Ответ 3
Я думал, что вы можете сделать что-то подобное и использовать DynamicProxy для реальной реализации.
def memo[T<:Product, R, F <: { def tupled: T => R }](f: F )(implicit m: Manifest[F]):F
Идея состоит в том, что, поскольку функции не имеют общего супер-типа, мы используем структурный тип для поиска чего-либо, что может быть скопировано (Function2-22, вам все еще нужен специальный случай Function1).
Я бросаю манифест, чтобы вы могли построить DynamicProxy из функции, которая является F
Tupling также должен помочь с memoization, так как вы просто поместите кортеж в Map [T, R]
Ответ 4
Это работает, потому что K
может быть типом кортежа, поэтому memo(x,y,z) { function of x, y, z }
работает:
import scala.collection.mutable
def memo[K,R](k: K)(f: => R)(implicit m: mutable.Map[K,R]) = m.getOrElseUpdate(k, f)
Неявный был единственным способом, который я мог видеть, чтобы нанести на карту чисто:
implicit val fibMap = new mutable.HashMap[Int,Int]
def fib(x: Int): Int = memo(x) {
x match {
case 1 => 1
case 2 => 1
case n => fib(n - 2) + fib(n - 1)
}
}
Похоже, должно быть возможно каким-то образом завернуть автоматический HashMap[K,R]
, чтобы вам не пришлось явно делать fibMap
(и повторно описывать тип).