2012-04-18 94 views
6

说我有一个函数带一个参数斯卡拉职能转变

def fun(x: Int) = x 

基于这一点,我想生成具有相同的调用约定一个新的功能,但会采用一些变换,它的参数在委托给原始函数之前。对于这一点,我可以

def wrap_fun(f: (Int) => Int) = (x: Int) => f(x * 2) 
wrap_fun(fun)(2) // 4 

一个怎么可能去这样做同样的事情,除了任何元数,只有拥有的参数的部分改造适用于普通的功能是什么?

def fun1(x: Int, y: Int) = x 
def fun2(x: Int, foo: Map[Int,Str], bar: Seq[Seq[Int]]) = x 

wrap_fun(fun1)(2, 4) // 4 
wrap_fun(fun2)(2, Map(), Seq()) // 4 

如何使wrap_fun定义使上述调用工作看起来像?

+0

Fwiw,这些东西在动态语言中可以非常简单:http://ideone.com/MYP2W。 – missingfaktor 2012-04-30 18:27:02

回答

6

这可以在相当直截了当地使用shapeless's设施进行了提炼过的功能参数数量,

import shapeless._ 
import HList._ 
import Functions._ 

def wrap_fun[F, T <: HList, R](f : F) 
    (implicit 
    hl : FnHListerAux[F, (Int :: T) => R], 
    unhl : FnUnHListerAux[(Int :: T) => R, F]) = 
     ((x : Int :: T) => f.hlisted(x.head*2 :: x.tail)).unhlisted 

val f1 = wrap_fun(fun _) 
val f2 = wrap_fun(fun1 _) 
val f3 = wrap_fun(fun2 _) 

样品REPL会话,

scala> f1(2) 
res0: Int = 4 

scala> f2(2, 4) 
res1: Int = 4 

scala> f3(2, Map(), Seq()) 
res2: Int = 4 

请注意,您不能立即应用包装的函数(在问题中)而不是通过一个赋值的val(正如我上面所做的那样),因为包装函数的显式参数列表将与隐含参数列表wrap_fun混淆。我们可以得到在问题的形式最接近的是要明确命名apply方法如下,

scala> wrap_fun(fun _).apply(2) 
res3: Int = 4 

scala> wrap_fun(fun1 _).apply(2, 4) 
res4: Int = 4 

scala> wrap_fun(fun2 _).apply(2, Map(), Seq()) 
res5: Int = 4 

这里的apply明确提及(含隐参数列表沿wrap_fun)语法标志着关闭第一个应用程序从第二个应用程序(具有显式参数列表的已转换函数)。

+0

这很酷!是否有可能在GitHub页面(无论是在自述文件还是在wiki上)获得更明确的文档(特别是,“FnHListerAux”和“FnUnHListerAux”)? – Destin 2012-04-19 15:31:37

+0

谢谢。是的,这是在我的TODO清单上,但如果您有时间和倾向,那么对文档的拉取请求将非常受欢迎:-) – 2012-04-19 15:42:07

2

由于采用不同数量参数的函数是不同的,无关的类型,所以不能一般地这样做。并没有别的。你将需要一个单独的方法为每个arity。

+2

我不会说'不行'。 [无形](https://github.com/milessabin/shapeless)有一些很酷的功能,可以用于抽象。由于[liftO](https://github.com/milessabin/shapeless/blob/master/src/main/scala/shapeless/lift.scala)可以与任意数量的函数一起工作,所以这应该是可能的。 – leedm777 2012-04-18 20:00:08

+1

@dave无形可以通过编写所有可能的情况 - 元组和函数只能到22位,因为Scala无法对它们进行抽象,所以每个元素都必须被定义。 – 2012-04-19 00:13:48

+1

@丹尼尔不,不需要列举所有的情况下做这个没有形状...看到我的答案。 – 2012-04-19 13:31:24

1

虽然我投票赞同路易吉的回答 - 因为,你知道......他的正确;斯卡拉有直接,内置支持这样的事情 - 值得注意的是,你要做的不是不可能;这只是让人感到有点痛苦,而且通常情况下,最好的做法是按照所需的方式实施一种单独的方法。

这就是说,虽然...我们实际上可以做到这一点HList s。如果您有兴趣尝试它,自然,您需要获得HList实施。我建议利用Miles Sabin的shapeless项目及其实施HList s。不管怎样,下面是完成一些类似于你仿佛其使用的一个例子是在寻找:

import shapeless._ 

trait WrapperFunner[T] { 
    type Inputs <: HList 
    def wrapFun(inputs: Inputs) : T 
} 

class WrapsOne extends WrapperFunner[Int] { 
    type Inputs = Int :: HNil 
    def wrapFun(inputs: Inputs) : Int = { 
    inputs match { 
     case num :: HNil => num * 2 
    } 
    } 
} 

class WrapsThree extends WrapperFunner[String] { 
    type Inputs = Int :: Int :: String :: HNil 
    def wrapFun(inputs: Inputs) : String = { 
    inputs match { 
     case firstNum :: secondNum :: str :: HNil => str + (firstNum - secondNum) 
    } 
    } 
} 

object MyApp extends App { 

    val wo = new WrapsOne 
    println(wo.wrapFun(1 :: HNil)) 
    println(wo.wrapFun(17 :: HNil)) 
    //println(wo.wrapFun(18 :: 13 :: HNil)) // Would give type error 

    val wt = new WrapsThree 
    println(wt.wrapFun(5 :: 1 :: "your result is: " :: HNil)) 
    val (first, second) = (60, 50) 
    println(wt.wrapFun(first :: second :: "%s minus %s is: ".format(first, second) :: HNil)) 
    //println(wt.wrapFun(1 :: HNil)) // Would give type error 

} 

运行MyApp结果:

2 
34 
your result is: 4 
60 minus 50 is: 10 

或者,扩展更接近你的具体情况:

import shapeless._ 

trait WrapperFunner[T] { 
    type Inputs <: HList 
    def wrapFun(inputs: Inputs) : T 
} 

trait WrapperFunnerBase extends WrapperFunner[Int] { 
    // Does not override `Inputs` 
    def wrapFun(inputs: Inputs) : Int = { 
    inputs match { 
     case (num: Int) :: remainder => num 
    } 
    } 
} 

class IgnoresNothing extends WrapperFunnerBase { 
    type Inputs = Int :: HNil 
} 

class IgnoresLastTwo extends WrapperFunnerBase { 
    type Inputs = Int :: Int :: String :: HNil 
} 

object MyApp extends App { 

    val in = new IgnoresNothing 
    println(in.wrapFun(1 :: HNil)) 
    println(in.wrapFun(2 :: HNil)) 
    //println(in.wrapFun(3 :: 4 :: HNil)) // Would give type error 

    val ilt = new IgnoresLastTwo 
    println(ilt.wrapFun(60 :: 13 :: "stupid string" :: HNil)) 
    println(ilt.wrapFun(43 :: 7 :: "man, that string was stupid..." :: HNil)) 
    //println(ilt.wrapFun(1 :: HNil)) // Would give type error 

} 

结果:

1 
2 
60 
43 
+0

你真的让我有点害怕!看到我的答案,使用无形的更简单的解决方案。 – 2012-04-19 13:32:49

+0

哎呀... s /平均/餐/ – 2012-04-19 13:56:17

+0

@MilesSabin是的,我预计我的方式不会是最好的。感谢您提供更优质的解决方案! – Destin 2012-04-19 15:27:14

6

像往常一样在斯卡拉,还有另一种方法来实现你想做的事情。

这是基于第一个参数与Function1compose一起讨好一个看法:

def fun1(x : Int)(y : Int) = x 
def fun2(x : Int)(foo : Map[Int, String], bar : Seq[Seq[Int]]) = x 

def modify(x : Int) = 2*x 

产生的类型,REPL显示你会:

fun1: (x: Int)(y: Int)Int 
fun2: (x: Int)(foo: Map[Int,String], bar: Seq[Seq[Int]])Int 
modify: (x: Int)Int 

,而是包装的功能fun1fun2,你compose他们,从技术上说,他们现在都是Function1对象。这使您可以像下面这样调用:

(fun1 _ compose modify)(2)(5) 
(fun2 _ compose modify)(2)(Map(), Seq()) 

这两者将返回4.当然,语法是不是很好,因为你必须添加_区分功能fun1的应用对象本身(在这种情况下,您想在其上调用compose方法)。

因此,路易吉关于这是不可能的论点仍然有效,但如果你有自由咖喱你的功能,你可以用这种很好的方式来做到这一点。