2012-04-18 148 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方法)。

因此,路易吉關於這是不可能的論點仍然有效,但如果你有自由咖喱你的功能,你可以用這種很好的方式來做到這一點。