2013-03-02 32 views
5

Có cách nào để gọi hàm Scala có tham số riêng lẻ không, cho một mảng (tương tự như JavaScript Spreads trong ECMAScript 6)?Tham số 'Spread' trong Scala?

ys = [10.0, 2.72, -3.14] 
f(x, ...ys); 

Cú pháp sạch sẽ là:

f(1, ys) 

nhưng điều đó không xuất hiện để thực hiện được. Thậm chí f(1, ys:_*) không hoạt động (và cũng không f(ys:_*), vì trình biên dịch báo cáo quá ít tham số - chỉ có thông số đầu tiên được điền).

Ví dụ:

def f (a:Int, b:Float, c:Float, d:Float): String 

val x = 1 
val ys = List(10.0, 2.72, -3.14) // note: non-Objects 
val result = f(x, ys) // intuitive, but doesn't work 

Trường hợp sử dụng: tiêm dữ liệu thử nghiệm (từ bộ sưu tập) vào các phương pháp hiện có chấp nhận các thông số cá nhân. Vì đây là những trường hợp thử nghiệm, nó hoàn toàn ổn nếu #params trong y không khớp và điều này cho phép lỗi thời gian chạy hoặc kết quả không chính xác.

Câu hỏi đặt ra là liệu Scala có cho phép cú pháp rõ ràng để gọi một hàm có tham số riêng lẻ hay không, cho dù đó là thiết kế tốt (mặc dù ý kiến ​​chắc chắn được chào đón).

+4

Điều gì sẽ xảy ra nếu 'ys' có ít hơn 3 thành phần trở lên? – huynhjl

+0

Quá tải hàm 'f' là những gì bạn nghĩ đến. Điều đó sẽ cho phép bạn gọi hàm theo cách bạn đã cung cấp, nhưng nó sẽ thất bại trong các tình huống khác –

Trả lời

3

Việc thoát khỏi danh sách dưới dạng bộ túp không phải là dễ dàng, vì các loại không khớp với nhau (nhiều hơn về điều này sau). Với đủ shoehorning và bôi trơn bất cứ điều gì phù hợp mặc dù:

"My hack" should { 
    "allow a function to be called with Lists" in { 

    def function(bar:String, baz:String)= bar+baz 


    //turn the function into something that accepts a tuple 
    val functionT = function _ 
    val functionTT = functionT.tupled 

    val arguments = List("bar", "baz") 

    //Give the compiler a way to try and turn a list into a tuple of the size you need 
    implicit def listToTuple(args:List[String]) = args match { 
     case List(a, b) => (a, b) 
     case _ => throw IllegalArgumentException("Trying to pass off %s as a pair".format(args)) 
    } 

    //Shove it in and hope for the best at runtime 
    val applied = functionTT(arguments) 
    applied === "barbaz" 
    } 
} 

Bạn có thể mở rộng phương pháp này bằng cách thêm các đối số thêm vào danh sách, hoặc bằng cách Schönfinkling các đối số ở hai nhóm khác nhau. Tôi sẽ không đi theo cách đó.

Từ nhận xét của tôi, bạn có thể nhận thấy rằng tôi không thích thiết kế khiến câu hỏi này bật lên. Mã tôi đã giới thiệu về cơ bản là mã đang bao hàm hàm trong một mặt tiền. Tại sao không làm điều đó đúng cách?

Nhìn vào Xịt, bạn có thể thấy rằng phương pháp hoàn chỉnh của họ chấp nhận một tấn thông số khác nhau hoàn toàn. Nỗ lực tiện lợi mà họ đã sử dụng cho việc này họ đã đặt tên là Magnet Pattern. Bạn có thể làm điều tương tự và giới thiệu các chuyển đổi tiềm ẩn đến nam châm của bạn cho các bộ dữ liệu khác nhau mà bạn chọn chấp nhận.

+0

+1 cho sự sáng tạo và một giải pháp khá sạch sẽ. Nhưng bất kỳ ý tưởng làm thế nào để a) gọi hàm với nhiều đối số hơn trước tuple, như trong ví dụ? và b) cho phép nó hoạt động đối với bất kỳ số tham số nào (không chỉ hai hoặc ba hoặc bao nhiêu mã được mã hóa cứng như trường hợp)? –

+1

Nếu bạn cần bất kỳ số tham số nào, bạn cần tạo mã hoặc phản chiếu. Với các tham số bổ sung, bạn có thể thao tác danh sách hoặc xử lý toàn bộ điều trong chuyển đổi ngầm định. Tôi sẽ cập nhật câu trả lời để đưa ra một số gợi ý, nhưng tôi sẽ chủ yếu thêm một lựa chọn tốt hơn. – iwein

+1

Có một cách để thực hiện việc này với các tính năng Macro mới trong phiên bản 2.10. Tôi không có thời gian để mã hóa nó. Mặc dù bạn có thể làm một cái gì đó như áp dụng (f, args ...) và nhận được kết quả bạn đang tìm kiếm (với an toàn loại). – jroesch

3

Tôi nghĩ bạn sẽ phải ném an toàn loại đi và sử dụng phản xạ.

scala> object Foo { 
| def doFoo(i:Int, s:String) = println(s * i) 
| } 

defined module Foo 

scala> val fooFunc = Foo.doFoo _ 
fooFunc: (Int, String) => Unit = <function2> 

scala> val applyMeth = fooFunc.getClass.getMethods()(0) 
applyMeth: java.lang.reflect.Method = public final void $anonfun$1.apply(int,java.lang.String) 

scala> val i:Integer = 5 

i: Integer = 5 

scala> val seq = Seq[Object](i,"do the foo! ") 
seq: Seq[Object] = List(5, "do the foo! ") 

scala> applyMeth.invoke(fooFunc, seq :_*) 
do the foo! do the foo! do the foo! do the foo! do the foo! 
res59: Object = null 

Tuy nhiên, trừ khi bạn đang tạo ra một số DSL và thực sự cần loại tính năng, tôi muốn cố gắng tìm một cách khác. Hoặc là quá tải các phương pháp nếu nó dưới sự kiểm soát của bạn hoặc bọc nó trong một số loại mặt tiền của lớp.

EDIT

Để answere câu hỏi Rubistro của trong các ý kiến.

a) Kỹ thuật này hoạt động như thế nào để gọi foo.doFoo? (Tức là, bạn không có đối tượng - chỉ là một thể hiện của một lớp định nghĩa doFoo.)

val fooFunc là trường hợp của Function2, ví dụ đó áp dụng hàm được gọi khi gọi applyMeth.invoke(fooFunc, seq :_*).

b) phương pháp này có cho phép các tham số được chuyển đến doFoo trước và sau các giá trị trong seq không?

Không trực tiếp. Để sử dụng điều này, bạn sẽ phải xây dựng trình tự của bạn trước khi gọi phương thức. Tuy nhiên, vì đó là một Chuỗi, bạn có thể dễ dàng thêm/thêm các giá trị vào chuỗi bạn đang sử dụng trước khi gọi phương thức. Bao bọc nó trong một người xây dựng có thể hữu ích, ví dụ:

class InvokationBuilder(meth:java.lang.reflect.Method, args: List[Object] = Nil) { 
    def invoke(instance:Object) = meth.invoke(instance, args.toSeq :_*) 
    def append(arg:Object) = new InvokationBuilder(meth, args :+ arg) 
    def prepend(arg:Object)= new InvokationBuilder(meth, arg :: args) 
} 
+1

a) Kỹ thuật này hoạt động như thế nào để gọi foo.doFoo? (Tức là, bạn không có đối tượng - chỉ là một thể hiện của một lớp định nghĩa doFoo.) B) phương thức này có cho phép các tham số được truyền tới doFoo * trước * và * sau * các giá trị trong 'seq' không? –

+0

@Rubistro Tôi đã cập nhật câu trả lời với một số giải thích thêm về câu hỏi của bạn. –

Các vấn đề liên quan