Câu hỏi Sự khác nhau giữa nhiều danh sách tham số và nhiều thông số cho mỗi danh sách trong Scala là gì?


Trong Scala người ta có thể viết (curried?) Chức năng như thế này

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

Sự khác biệt giữa những gì ở trên curriedFunc định nghĩa hàm với hai danh sách tham số và hàm với 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, đây là (curriedFunc(x))(y) và curriedFunc(x,y) nhưng tôi có thể viết def sum(x) (y) = x + y và như vậy sẽ là def sum2(x, y) = x + y

Tôi chỉ biết một sự khác biệt - đây là một phần chức năng được áp dụng. 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?


76
2017-07-23 20:48


gốc




Các câu trả lời:


Nói một cách nghiêm túc, đây không phải là một hàm curried, mà là một phương thức với nhiều danh sách đối số, mặc dù đã 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 ở vị trí của hàm được áp dụng một phần. (Xin lỗi vì những ví dụ 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 các dấu ngoặc nhọn thay vì dấu ngoặc đơn trông đẹp nếu danh sách đối số thứ hai bao gồm một hàm duy nhất, hoặc một đoạn. 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 cho thunk:

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 một 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 đó là một bất lợi mà bạn không thể làm điều đó trong danh sách đơn :)

// 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 có liên quan Tại sao Scala cung cấp cả hai danh sách tham số và nhiều tham số cho mỗi danh sách? . Tôi sẽ chỉ sao chép chúng ở đây, nhưng tín dụng đi đến Knut Arne Vedaa, Kevin Wright và extempore.

Đầ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

... sẽ không thể thực hiện được trong một danh sách đối số.

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

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 mà bạn có thể có và không ngầm tiềm ẩn, như implicit là 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

81
2017-07-23 23:21



Vì đó là câu trả lời được bình chọn nhiều nhất, tôi nghĩ 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ĩ tiêu đề nên được thay đổi thành, "Tại sao Scala cung cấp cả hai danh sách tham số và nhiều thông số cho 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


Có một sự khác biệt mà không được bao phủ bởi 0 __ 's xuất sắc câu trả lời: thông 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 mặc định trong một danh sách tham số khác, nhưng không phải trong một danh sách tham số.

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

39
2017-07-26 02:57



Lấy ví dụ đơn giản này để chìm 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ụ tốt, ngoại trừ tôi đã dành năm phút để tìm ra cách gọi nó: g(1)() trả về 3. g(1)(2) trả về 5. - Sapience


Đó là toàn bộ vấn đề, đó là các hình thức đã khóc và chưa được xử lý là tương đương! Như những người khác đã chỉ ra, một hoặc các hình thức khác có thể cú pháp thuận tiện hơn để làm việc với tùy thuộc vào tình hình, và đó là lý do duy nhất để thích cái này hơn cái 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 chức năng đã kết thúc, bạn vẫn có thể xây dựng chúng; đây chỉ là một sự không thể tránh khỏi 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 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 cú pháp thuận tiện cho việc làm một cái gì đó mà có thể có được anyway, nhưng với một chút verbosity.


18
2017-07-23 23:18





Hai dạng này là đẳng cấu. Sự khác biệt chính là các hàm curried dễ áp ​​dụng một phần, trong khi các hàm không được curried có cú pháp hơi đẹp hơn, ít nhất là trong Scala.


4
2017-07-23 20:55



Chẳng phải trước đây, các ví dụ là không phải chức năng đã kết hôn? 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à cứ như vậy cho đến khi có một cơ thể với tất cả các đối số được đóng lại. Liệu tôi có sai? - Jacek Laskowski