Sự khác biệt giữa danh sách nhiều tham số và nhiều tham số trên mỗi danh sách trong Scala là gì?


81

Trong Scala người ta có thể viết các hàm (curried?) Như thế này

def curriedFunc(arg1: Int) (arg2: String) = { ... }

Sự khác biệt giữa curriedFuncđịnh nghĩa hàm trên với hai danh sách tham số và các hàm có nhiều tham số trong một danh sách tham số duy nhất:

def curriedFunc(arg1: Int, arg2: String) = { ... }

Từ quan điểm toán học, điều này là (curriedFunc(x))(y)curriedFunc(x,y)nhưng tôi có thể viết def sum(x) (y) = x + yvà điều tương tự sẽdef sum2(x, y) = x + y

Tôi chỉ biết một sự khác biệt - đây là các chức năng được áp dụng một phần. Nhưng cả hai cách đều tương đương với tôi.

Có sự khác biệt nào khác không?

Câu trả lời:


88

Nói một cách chính xác, đây không phải là một hàm curry, mà là một phương thức có nhiều danh sách đối số, mặc dù phải thừa nhận rằng nó trông giống như một hàm.

Như bạn đã nói, danh sách nhiều đối số cho phép phương thức được sử dụng thay cho một hàm được áp dụng một phần. (Xin lỗi vì những ví dụ thường ngớ ngẩn mà tôi sử dụng)

object NonCurr {
  def tabulate[A](n: Int, fun: Int => A) = IndexedSeq.tabulate(n)(fun)
}

NonCurr.tabulate[Double](10, _)            // not possible
val x = IndexedSeq.tabulate[Double](10) _  // possible. x is Function1 now
x(math.exp(_))                             // complete the application

Một lợi ích khác là bạn có thể sử dụng dấu ngoặc nhọn thay vì dấu ngoặc đơn trông đẹp mắt nếu danh sách đối số thứ hai bao gồm một hàm duy nhất hoặc thunk. Ví dụ

NonCurr.tabulate(10, { i => val j = util.Random.nextInt(i + 1); i - i % 2 })

đấu với

IndexedSeq.tabulate(10) { i =>
  val j = util.Random.nextInt(i + 1)
  i - i % 2
}

Hoặc đối với tiếng sét:

IndexedSeq.fill(10) {
  println("debug: operating the random number generator")
  util.Random.nextInt(99)
}

Một ưu điểm khác là, bạn có thể tham khảo các đối số của danh sách đối số trước đó để xác định các giá trị đối số mặc định (mặc dù bạn cũng có thể nói rằng đó là một nhược điểm là bạn không thể làm điều đó trong danh sách đơn lẻ :)

// again I'm not very creative with the example, so forgive me
def doSomething(f: java.io.File)(modDate: Long = f.lastModified) = ???

Cuối cùng, có ba ứng dụng khác trong câu trả lời cho bài đăng liên quan Tại sao Scala cung cấp cả danh sách nhiều tham số và nhiều tham số trên mỗi danh sách? . Tôi sẽ chỉ sao chép chúng ở đây, nhưng tín dụng sẽ được chuyển đến Knut Arne Vedaa, Kevin Wright và người mở rộng.

Đầu tiên: bạn có thể có nhiều var args:

def foo(as: Int*)(bs: Int*)(cs: Int*) = as.sum * bs.sum * cs.sum

... mà sẽ không thể có trong một danh sách đối số.

Thứ hai, nó hỗ trợ kiểu suy luận:

def foo[T](a: T, b: T)(op: (T,T) => T) = op(a, b)
foo(1, 2){_ + _}   // compiler can infer the type of the op function

def foo2[T](a: T, b: T, op: (T,T) => T) = op(a, b)
foo2(1, 2, _ + _)  // compiler too stupid, unfortunately

Và cuối cùng, đây là cách duy nhất bạn có thể có các args ngầm định và không ngầm định, cũng như implicitmột công cụ sửa đổi cho toàn bộ danh sách đối số:

def gaga [A](x: A)(implicit mf: Manifest[A]) = ???   // ok
def gaga2[A](x: A, implicit mf: Manifest[A]) = ???   // not possible

2
Vì đây là câu trả lời được bình chọn nhiều nhất nên tôi nghĩ rằng tiêu đề của câu hỏi không còn tương ứng với câu trả lời của nó nữa. Tôi nghĩ rằng tiêu đề nên được thay đổi thành, nói, "Tại sao Scala cung cấp cả danh sách nhiều tham số và nhiều tham số trên mỗi danh sách?", Tức là nó đã được hợp nhất bởi các mẫu với stackoverflow.com/questions/4684185/… .
Jacek Laskowski

42

Có một sự khác biệt khác không được đề cập trong câu trả lời tuyệt vời của 0 __ : các tham số mặc định. Một tham số từ một danh sách tham số có thể được sử dụng khi tính toán giá trị mặc định trong danh sách tham số khác, nhưng không phải trong cùng một danh sách.

Ví dụ:

def f(x: Int, y: Int = x * 2) = x + y // not valid
def g(x: Int)(y: Int = x * 2) = x + y // valid

Lấy ví dụ đơn giản này để điều này đi sâu vào. Điều đó thực sự làm cho các tham số mặc định hữu ích hơn nhiều. Cảm ơn!
Mike McFarland

Ví dụ điển hình, ngoại trừ việc tôi đã dành năm phút để tìm cách gọi nó: g(1)()trả về 3. g(1)(2)trả về 5.
Sapience

19

Đó là toàn bộ vấn đề, là dạng cà ri và không sấy khô là tương đương nhau! Như những người khác đã chỉ ra, một hoặc các hình thức khác có thể là cú pháp thuận tiện hơn để làm việc tùy thuộc vào tình huống, và đó là lý do duy nhất để thích dạng này hơn dạng kia.

Điều quan trọng là phải hiểu rằng ngay cả khi Scala không có cú pháp đặc biệt để khai báo các hàm curried, bạn vẫn có thể xây dựng chúng; đây chỉ là một điều tất yếu toán học khi bạn có khả năng tạo ra các hàm trả về các hàm.

Để chứng minh điều này, hãy tưởng tượng rằng def foo(a)(b)(c) = {...}cú pháp không tồn tại. Sau đó, bạn vẫn có thể đạt được điều chính xác tương tự như vậy: def foo(a) = (b) => (c) => {...}.

Giống như nhiều tính năng trong Scala, đây chỉ là một sự tiện lợi về mặt cú pháp để làm điều gì đó có thể thực hiện được, nhưng với độ dài hơn một chút.


4

Hai dạng là đồng phân của nhau. Sự khác biệt chính là các hàm được nấu chín sẽ dễ áp ​​dụng hơn một phần, trong khi các hàm không được làm xoăn có cú pháp đẹp hơn một chút, ít nhất là trong Scala.


2
Không phải trước đó người ta đã nói rằng các ví dụ không phải là hàm curry? Tôi hiểu rằng một hàm curried chỉ có một đối số duy nhất và có thể trả về một hàm với một đối số duy nhất, v.v. cho đến khi có một nội dung với tất cả các đối số được đóng lại. Tôi có lầm không?
Jacek Laskowski
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.