반환 유형이 오버로드되지 않는 이유는 무엇입니까? (적어도 일반적으로 사용되는 언어로)


9

모든 프로그래밍 언어에 대해 잘 모르겠지만 일반적으로 반환 유형을 고려하여 메서드를 오버로드 할 가능성이 있습니다 (인수가 동일한 숫자 및 유형이라고 가정).

나는 이와 같은 것을 의미한다 :

 int method1 (int num)
 {

 }
 long method1 (int num)
 {

 }

그것은 프로그래밍에 큰 문제가 아니라 어떤 경우에는 그것을 환영했을 것입니다.

이러한 언어가 어떤 메소드가 호출되는지 구별 할 수있는 방법없이이를 지원할 수있는 방법은 없지만, 그 구문은 [int] method1 (num) 또는 [long] method1 (num)과 같이 간단 할 수 있습니다. 그렇게하면 컴파일러는 어떤 것이 호출 될지를 알 것입니다.

컴파일러의 작동 방식에 대해서는 잘 모르지만 그렇게하기 어려운 것처럼 보이지 않으므로 왜 그런 것이 일반적으로 구현되지 않는지 궁금합니다.

그와 같은 것이 지원되지 않는 이유는 무엇입니까?


어쩌면 두 반환 유형 사이에 암시 적 변환 (예 : 클래스 Foo및) 이없는 예를 사용하면 질문이 더 나을 것 Bar입니다.

그렇다면 왜 그러한 기능이 유용할까요?
— James Youngman

3
@JamesYoungman : 예를 들어 문자열을 다른 유형으로 구문 분석하기 위해 int read (String s), float read (String s) 등의 메소드를 사용할 수 있습니다. 오버로드 된 메소드 변형은 해당 유형에 대한 구문 분석을 수행합니다.
— Giorgio

그건 그렇고 정적으로 형식이 지정된 언어의 경우에만 문제입니다. Javascript 또는 Python과 같이 동적으로 유형이 지정된 언어에서는 여러 개의 반환 유형을 갖는 것이 일반적입니다.
— 로봇 고트

1
@StevenBurnap, 음. 예를 들어 JavaScript를 사용하면 함수 오버로드를 전혀 수행 할 수 없습니다. 따라서 이것은 실제로 함수 이름 오버로드를 지원하는 언어의 문제입니다.
— David Arno

답변:


19

유형 검사가 복잡합니다.

인수 유형을 기반으로 오버로드 만 허용하고 초 기자에서 변수 유형을 추론 할 수있는 경우 모든 유형 정보는 한 방향으로 구문 트리로 흐릅니다.

var x = f();

given      f   : () -> int  [upward]
given      ()  : ()         [upward]
therefore  f() : int        [upward]
therefore  x   : int        [upward]

유형 정보를 사용에서 변수를 추론하는 것과 같이 유형 정보가 양방향 으로 이동하도록 허용하는 경우 유형 을 결정하려면 구속 조건 솔버 (예 : Hindley-Milner 유형 시스템의 경우 알고리즘 W)가 필요합니다.

var x = parse("123");
print_int(x);

given      parse        : string -> T  [upward]
given      "123"        : string       [upward]
therefore  parse("123") : ∃T           [upward]
therefore  x            : ∃T           [upward]
given      print_int    : int -> ()    [upward]
therefore  print_int(x) : ()           [upward]
therefore  int -> ()    = ∃T -> ()     [downward]
therefore  ∃T           = int          [downward]
therefore  x            : int          [downward]

여기서 우리는 타입을 x해결되지 않은 타입 변수로 남겨 두어야했습니다 ∃T. 여기서 우리가 아는 것은 파싱 가능하다는 것입니다. 나중에 x구체적인 유형으로 사용될 때 제약 조건을 해결하고이를 결정하기 위해 충분한 정보가 있습니까?이 ∃T = int정보 는 유형 정보를 호출 표현식에서 구문 트리 로 전파합니다 x.

의 유형을 결정하지 못한 x경우이 코드도 오버로드되거나 호출자가 유형을 결정하거나 모호성에 대한 오류를보고해야합니다.

이를 통해 언어 디자이너는 다음과 같은 결론을 내릴 수 있습니다.

  • 구현에 복잡성을 추가합니다.

  • 병리학 적 경우 기하 급수적으로 유형 검사가 느려집니다.

  • 좋은 오류 메시지를 생성하기가 더 어렵습니다.

  • 현상 유지와 너무 다릅니다.

  • 나는 그것을 구현하고 싶지 않다.


1
또한 : 어떤 경우에는 컴파일러가 프로그래머가 전혀 예상하지 못한 선택을하여 버그를 찾기가 어렵다는 것을 이해하기가 어려울 것입니다.
— gnasher729

1
@ gnasher729 : 동의하지 않습니다. 나는이 기능을 가진 Haskell을 정기적으로 사용하며, 과부하 (즉, typeclass 인스턴스)의 선택에 물린 적이 없다. 뭔가 모호한 경우 유형 주석을 추가하도록 강요합니다. 그리고 나는 여전히 매우 유용하기 때문에 내 언어로 풀 타입 추론을 구현하기로 결정했습니다. 이 답변은 악마의 옹호자를 연주했습니다.
— Jon Purdy

4

모호하기 때문입니다. C #을 예로 사용 :

var foo = method(42);

어떤 과부하를 사용해야합니까?

아마, 그것은 약간 불공평했을 것입니다. 가상 언어로 말하지 않으면 컴파일러가 사용할 유형을 파악할 수 없습니다. 따라서 귀하의 언어에서는 암시 적 타이핑이 불가능하며 익명의 메소드와 Linq도 함께 사용됩니다 ...

이건 어때? 요점을 설명하기 위해 서명을 약간 재정의했습니다.

short method(int num) { ... }
int method(int num) { ... }

....

long foo = method(42);

int과부하 또는 과부하를 사용해야합니까 short? 우리는 단순히 알지 못합니다 [int] method1(num). 구문 으로 지정해야 합니다. 정직 하게 파싱하고 쓰는 것은 약간의 고통 입니다.

long foo = [int] method(42);

문제는 놀랍게도 C #의 일반 메소드와 비슷한 구문입니다.

long foo = method<int>(42);

(C ++과 Java는 비슷한 기능을 가지고 있습니다.)

즉, 언어 디자이너는 구문 분석을 단순화하고 훨씬 강력한 언어 기능을 사용하기 위해 다른 방식으로 문제를 해결하기로 결정했습니다.

컴파일러에 대해 잘 모른다고 말합니다. 문법과 파서에 대해 배우는 것이 좋습니다. 문맥 자유 문법이 무엇인지 이해하면 모호성이 왜 나쁜지에 대해 훨씬 더 잘 이해할 수 있습니다.


제네릭에 대한 좋은 지적,하지만 당신은 가미하여 한 것으로 나타났습니다 short하고 int.
— Robert Harvey

네, @RobertHarvey가 옳습니다. 요점을 설명하기 위해 예를 들어 고군분투했습니다. methodshort 또는 int를 반환하고 유형이 long으로 정의 되면 더 잘 작동 합니다.
— RubberDuck

조금 나아 보입니다.
— RubberDuck

리턴 타입 오버로드가있는 언어에서 타입 유추, 익명 서브 루틴 또는 모나드 이해를 할 수 없다는 주장은 구매하지 않습니다. 하스켈은 그것을하고 하스켈은 세 가지를 모두 가지고 있습니다. 파라 메트릭 다형성도 있습니다. long/ int/ 에 대한 요점은 short반환 유형 오버로드보다 하위 유형 지정 및 암시 적 변환의 복잡성에 대한 것입니다. 결국 숫자 리터럴 은 C ++, Java, C♯ 및 기타 여러 유형의 반환 유형에 과부하가 걸리므로 문제가되지 않습니다. 가장 구체적인 / 일반 유형을 선택하는 등의 규칙을 간단하게 만들 수 있습니다.
— Jörg W Mittag

@ JörgWMittag 내 요점은 불가능하게 만드는 것이 아니라 불필요하게 복잡하게 만드는 것입니다.
— RubberDuck

0

모든 언어 기능은 복잡성을 추가하므로 모든 기능에서 발생하는 불가피한 문제, 코너 케이스 및 사용자 혼란을 정당화 할 수있는 충분한 이점을 제공해야합니다. 대부분의 언어 에서이 언어는 정당화하기에 충분한 이점을 제공하지 않습니다.

대부분의 언어에서 표현식 method1(2)은 명확한 유형과 다소 예측 가능한 반환 값을 가질 것으로 예상합니다 . 그러나 반환 값에 오버로드를 허용하면 주변 컨텍스트를 고려하지 않고 해당 표현식이 일반적으로 무엇을 의미하는지 알 수 없습니다. unsigned long long foo()구현이 return method1(2)?로 끝나는 메소드 가있을 때 어떤 일이 발생하는지 고려하십시오 . 그것이 long-returning 과부하 또는 -returning 과부하 를 호출 int하거나 단순히 컴파일러 오류를 제공해야합니까?

또한 반환 유형에 주석을 달아 컴파일러를 도와야하는 경우 더 많은 구문을 발명 할뿐만 아니라 (위에 언급 된 기능이 존재하도록 허용하는 모든 비용을 발생시킵니다) 효과적으로 만드는 것과 동일한 일을하고 있습니다 "일반"언어로 된 다른 이름의 두 가지 방법 가 [long] method1(2)보다 직관적 long_method1(2)?


반면에 매우 강력한 정적 유형 시스템을 가진 Haskell과 같은 일부 기능 언어는 이러한 유형의 동작을 허용합니다. 유형 유추는 강력하기 때문에 반환 언어에 주석을 달 필요가 거의 없기 때문입니다. 그러나 이러한 언어는 모든 언어가 순수하고 참조가 투명해야 할뿐만 아니라 기존 언어보다 유형 안전을 실제로 시행하기 때문에 가능합니다. 대부분의 OOP 언어에서 실현 가능한 것은 아닙니다.


2
"모든 기능을 순수하고 참조가 투명해야하는 것과 함께": 리턴 타입 오버로드를 더 쉽게 만드는 방법은 무엇입니까?
— Giorgio

@Giorgio 그것은하지 않습니다-Rust는 기능 순결을 강요하지 않으며 여전히 리턴 타입 오버로드를 수행 할 수 있습니다 (Russ에서의 오버로드는 다른 언어와 매우 다르지만 (템플릿을 사용하여 오버로드 만 가능))
— Idan Arye

[long]과 [int] 부분은 명시 적으로 메소드를 호출하는 방법을 가지고 있었으며, 대부분의 경우 메소드를 호출하는 방법은 메소드 실행에 지정된 변수 유형에서 직접 추론 할 수 있습니다.
— user2638180

0

그것은 이다 스위프트에서 사용할 미세이 작동합니다. 분명히 양쪽에 모호한 유형을 가질 수 없으므로 왼쪽에 알려야합니다.

간단한 인 코드 / 디코드 API 에서 이것을 사용했습니다 .

public protocol HierDecoder {
  func dech() throws -> String
  func dech() throws -> Int
  func dech() throws -> Bool

즉 init, 객체 와 같이 매개 변수 유형이 알려진 호출은 매우 간단하게 작동합니다.

    private static let typeCode = "ds"
    static func registerFactory() {
        HierCodableFactories.Register(key:typeCode) {
            (from) -> HierCodable in
            return try tgDrawStyle(strokeColor:from.dech(), fillColor:from.dechOpt(), lineWidth:from.dech(), glowWidth: from.dech())
        }
    }
    func typeKey() -> String { return tgDrawStyle.typeCode }
    func encode(to:HierEncoder) {
        to.ench(strokeColor)
        to.enchOpt(fillColor)
        to.ench(lineWidth)
        to.ench(glowWidth)
    }

주의를 기울이면 dechOpt위 전화가 나타납니다. 호출 컨텍스트가 선택 사항이라는 기대를 일으킬 수 있기 때문에 미분기가 옵션을 반환하는 동일한 함수 이름을 오버로드하는 것이 너무 오류가 발생하기 어려운 어려운 방법을 발견했습니다.


-5
int main() {
    auto var1 = method1(1);
}

이 경우 컴파일러는 a) 모호하기 때문에 호출을 거부 할 수 있습니다 .b) 첫 번째 / 마지막 호출을 선택하십시오 .c) 유형을 떠나 var1유형 유추를 진행하고 다른 표현식 var1이 선택하는 사용 유형을 결정하자마자 올바른 구현. 결론적으로, 유형 유추가 사소하지 않은 경우를 보여주는 것은 그 유형 유추 이외의 점이 일반적으로 사소하지 않다는 것을 거의 증명하지 못합니다.
— back2dos

1
강력한 주장이 아닙니다. 예를 들어 Rust는 형식 유추를 많이 사용하며, 일부 경우, 특히 제네릭의 경우 어떤 형식이 필요한지 알 수 없습니다. 이러한 경우 유형 유추에 의존하는 대신 유형을 명시 적으로 지정하면됩니다.
— 8bittree

1
어 ... 오버로드 된 반환 유형을 보여주지 않습니다. method1특정 유형을 반환하려면 선언해야합니다.
— 로봇 고트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.