Tại sao trình biên dịch Scala không cho phép các phương thức quá tải với các đối số mặc định?


148

Mặc dù có thể có các trường hợp hợp lệ khi quá tải phương thức như vậy có thể trở nên mơ hồ, tại sao trình biên dịch không cho phép mã không mơ hồ tại thời gian biên dịch cũng như thời gian chạy?

Thí dụ:

// This fails:
def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

// This fails, too. Even if there is no position in the argument list,
// where the types are the same.
def foo(a: Int)   (b: Int = 42) = a + b
def foo(a: String)(b: String = "Foo") = a + b

// This is OK:
def foo(a: String)(b: Int) = a + b
def foo(a: Int)   (b: Int = 42) = a + b    

// Even this is OK.
def foo(a: Int)(b: Int) = a + b
def foo(a: Int)(b: String = "Foo") = a + b

val bar = foo(42)_ // This complains obviously ...

Có bất kỳ lý do tại sao những hạn chế này không thể được nới lỏng một chút?

Đặc biệt là khi chuyển đổi mã Java bị quá tải nặng sang các đối số mặc định của Scala là một điều rất quan trọng và thật không hay khi tìm hiểu sau khi thay thế nhiều phương thức Java bằng một phương thức Scala mà trình biên dịch / trình biên dịch áp đặt các hạn chế tùy ý.


18
"Hạn chế tùy ý" :-)
KajMagnus

1
Có vẻ như bạn có thể giải quyết vấn đề bằng cách sử dụng các đối số kiểu. object Test { def a[A](b: Int, c: Int, d: Int = 7): Unit = {}; def a[A](a:String, b: String = ""): Unit = {}; a(2,3,4); a("a");}
Biên

@ user1609012: Thủ thuật của bạn không hiệu quả với tôi. Tôi đã thử nó bằng Scala 2.12.0 và Scala 2.11.8.
Landfered Surfer

4
IMHO đây là một trong những điểm đau mạnh nhất ở Scala. Bất cứ khi nào tôi cố gắng cung cấp API linh hoạt, tôi thường gặp phải vấn đề này, đặc biệt là khi quá tải ứng dụng của đối tượng đồng hành (). Mặc dù tôi hơi thích Scala hơn Kotlin, nhưng trong Kotlin bạn có thể thực hiện kiểu quá tải này ...
rau diếp khối

Câu trả lời:


113

Tôi muốn trích dẫn Lukas Rytz (từ đây ):

Lý do là chúng tôi muốn một sơ đồ đặt tên xác định cho các phương thức được tạo để trả về các đối số mặc định. Nếu bạn viết

def f(a: Int = 1)

trình biên dịch tạo ra

def f$default$1 = 1

Nếu bạn có hai tình trạng quá tải với mặc định trên cùng một vị trí tham số, chúng ta sẽ cần một sơ đồ đặt tên khác. Nhưng chúng tôi muốn giữ cho mã byte được tạo ổn định qua nhiều lần chạy trình biên dịch.

Một giải pháp cho phiên bản Scala trong tương lai có thể là kết hợp các tên loại của các đối số không mặc định (các đối số ở đầu phương thức, phân tán các phiên bản quá tải) vào lược đồ đặt tên, ví dụ trong trường hợp này:

def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

nó sẽ giống như:

def foo$String$default$2 = 42
def foo$Int$default$2 = 42

Ai đó sẵn sàng viết một đề xuất SIP ?


2
Tôi nghĩ đề xuất của bạn ở đây rất có ý nghĩa và tôi không thấy điều gì sẽ quá phức tạp trong việc chỉ định / thực hiện nó. Về cơ bản, các loại tham số là một phần của ID của hàm. Trình biên dịch hiện đang làm gì với foo (String) và foo (Int) (nghĩa là các phương thức bị quá tải KHÔNG CÓ mặc định)?
Đánh dấu

Điều này có hiệu quả không khi giới thiệu Ký hiệu Hungary bắt buộc khi truy cập các phương thức Scala từ Java? Có vẻ như nó sẽ làm cho các giao diện trở nên cực kỳ dễ vỡ, buộc người dùng phải cẩn thận khi các tham số loại cho các chức năng thay đổi.
blast_hardcheese 17/12/14

Ngoài ra, những gì về các loại phức tạp? A with B, ví dụ?
blast_hardcheese 17/12/14

66

Sẽ rất khó để có được một thông số chính xác và dễ đọc cho các tương tác của quá tải độ phân giải với các đối số mặc định. Tất nhiên, đối với nhiều trường hợp riêng lẻ, như trường hợp được trình bày ở đây, thật dễ dàng để nói điều gì sẽ xảy ra. Nhưng như vậy là chưa đủ. Chúng tôi cần một thông số quyết định tất cả các trường hợp góc có thể. Quá tải độ phân giải đã rất khó để chỉ định. Thêm các đối số mặc định trong hỗn hợp sẽ làm cho nó khó hơn. Đó là lý do tại sao chúng tôi đã chọn cách tách hai.


4
Cảm ơn câu trả lời của bạn. Điều có lẽ làm tôi bối rối là về cơ bản mọi nơi khác, trình biên dịch chỉ phàn nàn nếu thực sự có sự mơ hồ. Nhưng ở đây trình biên dịch phàn nàn vì có thể có những trường hợp tương tự mà sự mơ hồ có thể xảy ra. Vì vậy, trong trường hợp đầu tiên, trình biên dịch chỉ phàn nàn nếu có vấn đề đã được chứng minh, nhưng trong trường hợp thứ hai, hành vi của trình biên dịch kém chính xác hơn và gây ra lỗi cho mã "dường như hợp lệ". Nhìn thấy điều này với nguyên tắc ít ngạc nhiên nhất, điều này thật đáng tiếc.
soc

2
Liệu "Sẽ rất khó để có được một thông số chính xác và dễ đọc [...]" có nghĩa là có khả năng thực tế là tình hình hiện tại có thể được cải thiện nếu ai đó bước lên với một đặc điểm kỹ thuật và / hoặc thực hiện tốt? Tình hình hiện tại imho giới hạn khả năng sử dụng của các tham số được đặt tên / mặc định khá nhiều ...
soc

Có một quy trình để đề xuất thay đổi cho thông số kỹ thuật. scala-lang.org/node/233
James Iry

2
Tôi có một số ý kiến (xem ý kiến ​​của tôi bên dưới câu trả lời được liên kết) về Scala làm cho quá tải nhăn mặt và một công dân hạng hai. Nếu chúng tôi tiếp tục cố tình làm suy yếu quá tải trong Scala, chúng tôi sẽ thay thế việc gõ bằng tên, mà IMO là một hướng hồi quy.
Shelby Moore III

10
Nếu Python có thể làm, tôi không thể thấy bất kỳ lý do chính đáng nào khiến Scala không thể. Đối số về độ phức tạp là một lý do tốt: thực hiện tính năng này sẽ làm cho Scale bớt phức tạp hơn từ góc nhìn của người dùng. Đọc các câu trả lời khác và bạn sẽ thấy mọi người phát minh ra những thứ rất phức tạp chỉ để giải quyết vấn đề thậm chí không tồn tại từ quan điểm của người dùng.
Richard Gomes

12

Tôi không thể trả lời câu hỏi của bạn, nhưng đây là một cách giải quyết:

implicit def left2Either[A,B](a:A):Either[A,B] = Left(a)
implicit def right2Either[A,B](b:B):Either[A,B] = Right(b)

def foo(a: Either[Int, String], b: Int = 42) = a match {
  case Left(i) => i + b
  case Right(s) => s + b
}

Nếu bạn có hai danh sách arg rất dài chỉ khác nhau trong một arg, nó có thể gây rắc rối ...


1
Chà, tôi đã cố gắng sử dụng các đối số mặc định để làm cho mã của mình ngắn gọn và dễ đọc hơn ... thực sự tôi đã thêm một chuyển đổi ngầm định cho lớp trong một trường hợp chỉ chuyển đổi loại thay thế thành loại được chấp nhận. Nó chỉ cảm thấy xấu xí. Và cách tiếp cận với các đối số mặc định sẽ chỉ hoạt động!
soc

Bạn nên cẩn thận với các chuyển đổi như vậy, kể từ khi họ áp dụng cho tất cả các công dụng của Eithervà không chỉ cho foo- Bằng cách này, bất cứ khi nào một Either[A, B]giá trị được yêu cầu, cả hai ABđược chấp nhận. Thay vào đó, ta nên định nghĩa một loại chỉ được chấp nhận bởi các hàm có đối số mặc định (như fooở đây), nếu bạn muốn đi theo hướng này; Tất nhiên, nó trở nên ít rõ ràng hơn cho dù đây là một giải pháp thuận tiện.
Blaisorblade

9

Điều làm việc cho tôi là xác định lại (kiểu Java) các phương thức nạp chồng.

def foo(a: Int, b: Int) = a + b
def foo(a: Int, b: String) = a + b
def foo(a: Int) = a + "42"
def foo(a: String) = a + "42"

Điều này đảm bảo trình biên dịch độ phân giải bạn muốn theo các tham số hiện tại.


3

Đây là một khái quát về câu trả lời @Landei:

Những gì bạn thực sự muốn:

def pretty(tree: Tree, showFields: Boolean = false): String = // ...
def pretty(tree: List[Tree], showFields: Boolean = false): String = // ...
def pretty(tree: Option[Tree], showFields: Boolean = false): String = // ...

Workarround

def pretty(input: CanPretty, showFields: Boolean = false): String = {
  input match {
    case TreeCanPretty(tree)       => prettyTree(tree, showFields)
    case ListTreeCanPretty(tree)   => prettyList(tree, showFields)
    case OptionTreeCanPretty(tree) => prettyOption(tree, showFields)
  }
}

sealed trait CanPretty
case class TreeCanPretty(tree: Tree) extends CanPretty
case class ListTreeCanPretty(tree: List[Tree]) extends CanPretty
case class OptionTreeCanPretty(tree: Option[Tree]) extends CanPretty

import scala.language.implicitConversions
implicit def treeCanPretty(tree: Tree): CanPretty = TreeCanPretty(tree)
implicit def listTreeCanPretty(tree: List[Tree]): CanPretty = ListTreeCanPretty(tree)
implicit def optionTreeCanPretty(tree: Option[Tree]): CanPretty = OptionTreeCanPretty(tree)

private def prettyTree(tree: Tree, showFields: Boolean): String = "fun ..."
private def prettyList(tree: List[Tree], showFields: Boolean): String = "fun ..."
private def prettyOption(tree: Option[Tree], showFields: Boolean): String = "fun ..."

1

Một trong những kịch bản có thể là


  def foo(a: Int)(b: Int = 10)(c: String = "10") = a + b + c
  def foo(a: Int)(b: String = "10")(c: Int = 10) = a + b + c

Trình biên dịch sẽ bị nhầm lẫn về cái nào để gọi. Để ngăn ngừa các nguy hiểm có thể khác, trình biên dịch sẽ cho phép nhiều nhất một phương thức bị quá tải có các đối số mặc định.

Chỉ là phỏng đoán của tôi :-)


0

Hiểu biết của tôi là có thể có các xung đột tên trong các lớp được biên dịch với các giá trị đối số mặc định. Tôi đã thấy một cái gì đó dọc theo những dòng này được đề cập trong một số chủ đề.

Thông số đối số được đặt tên ở đây: http://www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017:29/named-args.pdf

Nó nói:

 Overloading If there are multiple overloaded alternatives of a method, at most one is
 allowed to specify default arguments.

Vì vậy, tại thời điểm hiện tại, nó sẽ không hoạt động.

Bạn có thể làm một cái gì đó giống như những gì bạn có thể làm trong Java, ví dụ:

def foo(a: String)(b: Int) =  a + (if (b > 0) b else 42)
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.