대부분의 프로그래밍 언어가 함수에서 단일 값 반환 만 지원하는 이유는 무엇입니까? [닫은]


118

대부분의 (?) 프로그래밍 언어의 함수가 여러 입력 매개 변수를 지원하지만 하나의 반환 값만 지원하도록 설계된 이유가 있습니까?

대부분의 언어에서 매개 변수를 사용하거나 포인터를 반환하거나 구조체 / 클래스를 정의 / 반환하여 이러한 제한을 "해결"할 수 있습니다. 그러나 프로그래밍 언어가 여러 개의 리턴 값을보다 "자연적인"방식으로 지원하도록 설계되지 않은 것은 이상하게 보입니다.

이에 대한 설명이 있습니까?


40
배열을 반환 할 수 있습니다 ...
nathan hayfield

6
그렇다면 왜 하나의 논쟁 만 허용할까요? 연설과 관련이 있다고 생각합니다. 몇 가지 아이디어를 생각해보십시오. 운이 좋거나 불행한 사람에게 귀 기울여 들으려면 다시 한 번 생각하십시오. 답은 거의 의견과 같습니다. "this"는 "these"로하는 것입니다.
Erik Reppen

30
파이썬의 접근 방식은 매우 간단하고 우아하다고 생각합니다. 여러 값을 반환 해야하는 경우 튜플을 반환하면 튜플 def f(): return (1,2,3)풀기를 사용하여 튜플을 "분할"할 수 있습니다 a,b,c = f() #a=1,b=2,c=3. 배열을 생성하고 요소를 수동으로 추출 할 필요가 없으며 새로운 클래스를 정의 할 필요가 없습니다.
Bakuriu

7
Matlab에 가변 개수의 반환 값이 있음을 알려줄 수 있습니다. 출력 인수의 수는 호출 서명 (예 : [a, b] = f()vs. [a, b, c] = f())에 의해 결정되며 내부 f에서 확보 됩니다 nargout. 나는 Matlab의 열렬한 팬은 아니지만 실제로는 실제로 매우 유용합니다.
gerrit

5
나는 대부분의 프로그래밍 언어가 그런 식으로 설계 된다면 논쟁의 여지가 있다고 생각합니다 . 프로그래밍 언어의 역사에는 파스칼, C, C ++, 자바, 클래식 VB와 같은 인기있는 언어가 있었지만 오늘날에는 여러 리턴을 허용하는 더 많은 팬을 얻는 많은 다른 언어가 있습니다. 가치.
Doc Brown

답변:


58

Python과 같은 일부 언어는 기본적으로 여러 반환 값을 지원하는 반면 C #과 같은 일부 언어 는 기본 라이브러리를 통해 지원합니다.

그러나 일반적으로이를 지원하는 언어에서도 여러 반환 값은 느슨하기 때문에 자주 사용되지 않습니다.

  • 여러 값을 반환하는 함수는 명확하게 이름을 지정하기 어렵습니다 .
  • 반환 값의 순서를 잘못 착각하기 쉽습니다.

    (password, username) = GetUsernameAndPassword()  
    

    (이와 같은 이유로 많은 사람들이 함수에 너무 많은 매개 변수를 갖는 것을 피합니다. 어떤 사람들은 함수가 같은 유형의 두 매개 변수를 가져서는 안된다고 말하기까지합니다!)

  • OOP 언어는 이미 여러 반환 값에 대한 더 나은 대안을 가지고 있습니다 : 클래스.
    보다 강력한 형식을 가지며 반환 값을 하나의 논리 단위로 그룹화하고 반환 값의 이름 (속성)을 모든 용도에서 일관되게 유지합니다.

그것들 꽤 편리한 곳은 하나의 함수에서 여러 개의 반환 값을 다른 입력 매개 변수로 사용할 수있는 언어 (예 : Python)입니다. 그러나 이것이 클래스를 사용하는 것보다 더 나은 디자인 인 사용 사례는 상당히 슬림합니다.


50
튜플을 반환하면 여러 항목이 반환된다고 말하기 어렵습니다. 하나의 튜플을 반환 합니다. 작성한 코드는 구문 설탕을 사용하여 깔끔하게 압축을 풉니 다.

10
@ Lego : 구별이 보이지 않습니다. 튜플은 여러 값으로 정의됩니다. 그렇지 않은 경우 "다중 반환 값"을 어떻게 고려 하시겠습니까?
BlueRaja-대니 Pflughoeft

19
정말 헷갈리는 구별이지만 비어있는 Tuple을 고려하십시오 (). 그게 하나입니까 아니면 제로입니까? 개인적으로 한 가지만 말씀 드리겠습니다. 할당 할 수있는 x = ()것처럼 잘 할당 할 수 있습니다 x = randomTuple(). 후자에서 반환 된 튜플이 비어 있거나없는 경우에도 반환 된 튜플을에 할당 할 수 있습니다 x.

19
... 튜플을 다른 용도로 사용할 수 없다고 결코 주장하지 않았습니다. 하지만 그 주장 "는 튜플을 지원, 파이썬 여러 반환 값을 지원하지 않습니다" 단순히되고있는 매우 무의미한 현학적. 이것은 여전히 ​​정답입니다.
BlueRaja-대니 Pflughoeft

14
튜플이나 클래스 모두 "다중 값"이 아닙니다.
Andres F.

54

함수는 계산을 수행하고 결과를 반환하는 수학적 구성이기 때문입니다. 실제로, 소수의 프로그래밍 언어가 아닌 "언더 후드"는 하나의 입력과 하나의 출력에만 초점을 맞추고 여러 입력은 입력을 둘러싼 얇은 래퍼로, 단일 값 출력이 작동하지 않을 때 단일 응집성 구조 (또는 튜플 또는 Maybe)가 출력이됩니다 ( "단일"반환 값이 많은 값으로 구성되어 있음).

프로그래머가 out매개 변수가 제한된 시나리오에만 유용한 어색한 구성이라는 것을 알았 기 때문에 이것은 변경되지 않았습니다 . 다른 많은 것들과 마찬가지로 필요 / 수요가 없기 때문에 지원이 없습니다.


5
@FrustratedWithFormsDesigner- 최근 질문 에서 약간 올라 왔습니다 . 한 손으로 20 년 동안 여러 개의 출력을 원했던 횟수를 셀 수 있습니다.
Telastyn

61
수학의 함수와 대부분의 프로그래밍 언어의 함수는 매우 다른 두 가지 짐승입니다.
tdammers

17
초기에 @tdammers는 생각이 매우 유사했습니다. 컴퓨터 아키텍처보다 수학의 영향을 많이받는 포트란, 파스칼 등.

9
@tdammers-어떻게 그렇게? 나는 대부분의 언어에서 결국 람다 미적분학으로 끝납니다-하나의 입력, 하나의 출력, 부작용 없음. 그 밖의 모든 것은 그 위에 시뮬레이션 / 해킹입니다. 프로그래밍 기능은 여러 입력이 동일한 출력을 산출 할 수 있다는 의미에서 순수하지 않을 수 있지만 그 정신은 존재합니다.
Telastyn

16
@SteveEvers : 불행히도 "function"이라는 이름이보다 적절한 "procedure"또는 "routine"대신 명령형 프로그래밍에서 사용되었습니다. 함수형 프로그래밍에서 함수는 수학 함수와 훨씬 더 비슷합니다.
tdammers

35

수학에서 "잘 정의 된"함수 는 주어진 입력에 대해 하나의 출력 만있는 함수 입니다 (부수적으로 단일 입력 함수 만 가질 수 있으며 여전히 currying을 사용하여 여러 입력을 얻을 수 있음 ).

다중 값 함수 (예 : 양의 정수의 제곱근)의 경우 컬렉션 또는 값 시퀀스를 반환하는 것으로 충분합니다.

당신이 말하고있는 함수의 유형 (예 : 다른 유형의 여러 값을 반환하는 함수 )의 경우 나는 당신이 생각하는 것과 약간 다르게 보입니다. 더 유용한 데이터 구조. 예를 들어, *.TryParse(...)메소드 Maybe<T>가 out 매개 변수를 사용하는 대신 모나드를 반환하는 것이 좋습니다. F #에서이 코드를 생각해보십시오.

let s = "1"
match tryParse s with
| Some(i) -> // do whatever with i
| None -> // failed to parse

컴파일러 / IDE / 분석 지원은 이러한 구성에 매우 좋습니다. 이것은 매개 변수에 대한 "필요한"부분을 많이 해결합니다. 솔직히 말해서, 이것이 해결책이 아닌 다른 방법은 따로 생각할 수 없습니다.

다른 시나리오-내가 기억할 수없는 시나리오-간단한 Tuple로 충분합니다.


1
또한 C #으로 작성할 수 있기를 정말로 var (value, success) = ParseInt("foo");원합니다 (int, bool) ParseInt(string s) { }. 나는 이것이 제네릭으로 수행 할 수 있다는 것을 알고 있지만 여전히 멋진 언어 추가를 할 것입니다.
절망의 그리움

10
@GrimaceofDespair 당신이 정말로 원하는 것은 여러 반환 값이 아닌 구문을 파괴하는 것입니다.
Domenic

2
@warren : 그렇습니다. 여기를보고 그러한 해결책이 연속적이지 않다는 점에 유의하십시오. en.wikipedia.org/wiki/ 잘 정의
Steven Evers

4
잘 정의 된 수학적 개념은 함수가 반환하는 출력 수와 관련이 없습니다. 입력이 동일하면 출력이 항상 동일하다는 것을 의미합니다. 엄밀히 말하면 수학 함수는 하나의 값을 반환하지만 그 값은 종종 튜플입니다. 수학자에게는 본질적으로 이것과 여러 값을 반환하는 것 사이에는 차이가 없습니다. 수학 함수가 그다지 매력적이지 않기 때문에 프로그래밍 함수는 하나의 값만 반환해야한다고 주장합니다.
Michael Siler

1
@MichaelSiler (나는 당신의 의견에 동의)하지만 그 주장을 유의하시기 바랍니다 되돌릴 수 있습니다 : "프로그램 기능들이 하나의 튜플 값을 반환 할 수 있기 때문에 여러 값을 반환 매우 설득력없는 수의 인수 것을 하나":)
안드레스 F.

23

함수가 반환 할 때 어셈블리에 사용 된 패러다임을 보면 이미 말한 것 외에도 특정 레지스터의 반환 객체에 대한 포인터를 남깁니다. 변수 / 다중 레지스터를 사용한 경우 호출 함수는 함수가 라이브러리에있는 경우 반환 된 값을 가져올 위치를 알 수 없습니다. 따라서 라이브러리에 연결하는 것이 어려워지고 반환 가능한 포인터 수를 임의로 설정하는 대신 하나를 사용했습니다. 고급 언어는 똑같은 변명을 가지고 있지 않습니다.


아! 매우 흥미로운 세부 사항. +1!
Steven Evers

이것이 정답입니다. 보통 사람들은 컴파일러를 만들 때 대상 머신에 대해 생각합니다. 또 다른 비유는 대상 시스템에서 지원하기 때문에 int, float, char / string 등이있는 이유입니다. 대상이 베어 메탈 (예 : jvm)이 아니더라도 너무 많이 에뮬레이션하지 않으면서도 적절한 성능을 얻으려고합니다.
imel96

26
... 여러 값을 함수에 전달하기위한 호출 규칙을 정의 할 수있는 것과 같은 방식으로 함수에서 여러 값을 반환하기위한 호출 규칙을 쉽게 정의 할 수 있습니다. 이것은 답이 아닙니다. -1
BlueRaja-대니 Pflughoeft

2
스택 기반 실행 엔진 (JVM, CLR)이 여러 반환 값을 고려 / 허용했는지 알고있는 것이 흥미로울 것입니다. 매우 쉬워야합니다. 호출자는 올바른 수의 인수를 푸시하는 것처럼 올바른 수의 값을 팝해야합니다!
Lorenzo Dematté

1
@David 아니오, cdecl 은 (이론적으로) 무제한의 매개 변수를 허용합니다 ( varargs 기능 이 가능한 이유 ) . 일부 C 컴파일러는 함수 당 수십 또는 백 개의 인수로 제한 할 수 있지만 여전히 합리적이라고 생각합니다 -_-
BlueRaja-Danny Pflughoeft

18

과거에 여러 반환 값을 사용한 많은 유스 케이스는 더 이상 현대 언어 기능으로 더 이상 필요하지 않습니다. 오류 코드를 반환 하시겠습니까? 예외를 던지거나를 반환합니다 Either<T, Throwable>. 선택적인 결과를 반환하고 싶습니까? 을 반환합니다 Option<T>. 여러 유형 중 하나를 반환 하시겠습니까? Either<T1, T2>또는 태그가 지정된 유니온을 반환합니다 .

그리고 실제로 여러 값을 반환 해야하는 경우에도 현대 언어는 일반적으로 튜플 또는 일종의 데이터 구조 (목록, 배열, 사전) 또는 객체뿐만 아니라 어떤 형태의 구조화 바인딩 또는 패턴 일치를 지원하여 패키징을 만듭니다. 여러 값을 단일 값으로 만든 다음 다시 여러 값으로 간단하게 파괴합니다.

다음은 여러 값 반환을 지원 하지 않는 언어의 몇 가지 예입니다 . 여러 반환 값에 대한 지원을 추가하여 새로운 언어 기능의 비용을 상쇄하기 위해 표현을 크게 표현하는 방법을 실제로 알지 못합니다.

루비

def foo; return 1, 2, 3 end

one, two, three = foo

one
# => 1

three
# => 3

파이썬

def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3

스칼라

def foo = (1, 2, 3)

val (one, two, three) = foo
// => one:   Int = 1
// => two:   Int = 2
// => three: Int = 3

하스켈

let foo = (1, 2, 3)

let (one, two, three) = foo

one
-- > 1

three
-- > 3

펄 6

sub foo { 1, 2, 3 }

my ($one, $two, $three) = foo

$one
# > 1

$three
# > 3

1
한 가지 측면은 Matlab과 같은 일부 언어에서 함수가 반환하는 값의 수에 유연 할 수 있다는 것입니다. 위의 내 의견을 참조하십시오 . Matlab에는 내가 좋아하지 않는 많은 측면이 있지만 이것은 Matlab에서 Python으로 포팅 할 때 놓칠 수있는 몇 가지 (아마도 유일한) 기능 중 하나입니다.
gerrit

1
그러나 파이썬이나 루비와 같은 동적 언어는 어떻습니까? Matlab sort함수 와 같은 것을 작성한다고 가정 sorted = sort(array)하십시오. 정렬 된 배열 만 [sorted, indices] = sort(array)반환하고 둘 다 반환합니다. 파이썬에서 생각할 수있는 유일한 방법 sortsort(array, nout=2)또는 의 행 을 따라 플래그를 전달하는 것 sort(array, indices=True)입니다.
gerrit

2
@MikeCellini 나는 그렇게 생각하지 않습니다. 함수는 함수가 호출 된 출력 인수 수 ()를 알려주고 [a, b, c] = func(some, thing)그에 따라 작동합니다. 예를 들어, 첫 번째 출력 인수를 계산하는 것이 저렴하지만 두 번째 출력 인수를 계산하는 것이 비싼 경우에 유용합니다. Matlabs와 동등한 nargout런타임을 사용할 수 있는 다른 언어에는 익숙하지 않습니다 .
gerrit

1
@gerrit 파이썬에서 올바른 해결책은 다음과 같습니다 sorted, _ = sort(array).
Miles Rout

1
@MilesRout : 그리고 sort함수는 인덱스를 계산할 필요가 없다고 말할 수 있습니까? 멋지다, 나는 몰랐다.
Jörg W Mittag

12

단일 반환 값이 널리 사용되는 실제 이유는 많은 언어에서 사용되는 표현식입니다. x + 1머리글 표현을 조각으로 나누고 각 조각의 값을 결정하여 머리에 표현을 평가하기 때문에 이미 단일 반환 값의 관점에서 생각하고있는 것처럼 표현할 수있는 모든 언어에서 . 보고 x값이 3 (예 : 3)이라고 결정하고 1을보고x + 1전체의 값이 4라고 결정하기 위해 모두 합치십시오. 표현의 각 구문 부분에는 다른 수의 값이 아닌 하나의 값이 있습니다. 그것은 모든 사람들이 기대하는 표현의 자연스러운 의미입니다. 함수가 한 쌍의 값을 반환하더라도 두 값의 작업을 수행하는 하나의 값을 실제로 반환하는 것입니다. 단일 컬렉션으로 래핑되지 않은 두 개의 값을 반환하는 함수의 아이디어는 너무 이상하기 때문입니다.

사람들은 함수가 둘 이상의 값을 반환하도록하는 대체 의미 체계를 다루기를 원하지 않습니다. 예를 들어, Forth 와 같은 스택 기반 언어 에서는 각 함수가 단순히 스택의 맨 위를 수정하고 입력을 터 뜨리고 출력을 자유롭게 밀어 넣기 때문에 원하는만큼의 반환 값을 가질 수 있습니다. 그렇기 때문에 Forth에는 일반 언어와 같은 표현이 없습니다.

은 함수가 여러 값을 반환하는 것처럼 행동 할 수있는 또 다른 언어입니다. 방법 목록은 "보간" 펄이 우리에게 같은 목록 제공에 (1, foo(), 3)펄이 기대 모르는 대부분의 사람들 등 3 개 요소를 가질 수있는,하지만 그냥 간단하게 만 2 요소, 4 개 요소, 또는에 따라 요소의 더 많은 수를 가질 수 foo(). Perl 의리스트 는 전개 되어 구문리스트가 항상리스트의 의미를 갖지는 않습니다. 그것은 단지 더 큰 목록의 일부일 수 있습니다.

함수가 여러 값을 반환하도록하는 또 다른 방법은식이 여러 값을 가질 수 있고 각 값이 가능성을 나타내는 대체 식 의미론을 갖는 것입니다. x + 1다시 가져 오지만 이번에 x는 {3, 4}의 두 값 x + 1이 있고 그 값 이 {4, 5}이고 그 값 x + x이 {6, 8} 또는 {6, 7, 8} 일 것이라고 상상해보십시오. 하나 개의 평가를 위해 다수의 값을 사용할 수 있는지 여부에 따라 x. 이와 같은 언어는 Prolog 가 쿼리에 여러 답변을 제공하는 데 사용하는 것과 유사한 역 추적을 사용하여 구현 될 수 있습니다 .

간단히 말해, 함수 호출은 단일 구문 단위이고 단일 구문 단위는 모두 우리가 알고 사랑하는 표현 의미론에서 단일 값을 갖습니다. 다른 의미 체계는 Perl, Prolog 또는 Forth와 같은 이상한 일을하게합니다.


9

이 답변 에서 제안했듯이 하드웨어 디자인의 문제이지만 언어 디자인의 전통도 중요한 역할을합니다.

함수가 반환하면 특정 레지스터의 반환 객체에 대한 포인터를 남깁니다.

Fortran, Lisp 및 COBOL의 세 가지 첫 번째 언어 중 첫 번째 언어는 수학을 모델로 한 단일 반환 값을 사용했습니다. 두 번째는 목록과 같은 방식으로 임의의 수의 매개 변수를 반환했습니다 (목록의 주소 인 단일 매개 변수 만 전달하고 반환한다고 주장 할 수도 있음). 세 번째는 0 또는 하나의 값을 반환합니다.

이 첫 번째 언어는 그 이후의 언어 디자인에 많은 영향을 미쳤지 만, 여러 값을 반환 한 유일한 언어 인 Lisp는 큰 인기를 얻지 못했습니다.

C가 그 언어에 영향을 받았을 때 C는 하드웨어 리소스의 효율적인 사용에 중점을 두어 C 언어와 C 언어를 구현 한 기계 코드 사이의 밀접한 관계를 유지했습니다. "auto"대 "register"변수와 같은 가장 오래된 기능 중 일부는 디자인 철학의 결과입니다.

또한 어셈블리 언어는 주류 개발 단계에서 단계적으로 폐지되기 시작한 80 년대까지 널리 사용되었다는 점도 지적해야합니다. 컴파일러를 작성하고 언어를 만든 사람들은 어셈블리 에 대해 잘 알고 있었고 대부분 가장 잘 작동하는 것을 유지했습니다.

이 표준에서 벗어난 대부분의 언어는 큰 인기를 얻지 못했기 때문에 언어 디자이너의 결정에 영향을 미치는 강력한 역할을 수행하지 않았습니다.

어셈블리 언어에 대해 알아 보겠습니다. 먼저 Apple II 및 VIC-20 마이크로 컴퓨터에서 널리 사용되는 1975 년 마이크로 프로세서 인 6502를 살펴 보겠습니다 . 프로그래밍 언어가 시작될 때 30 년 전의 20 대 컴퓨터에 비해 강력하지만 당시의 메인 프레임과 미니 컴퓨터에 사용 된 것에 비해 매우 약했습니다.

기술적 인 설명을 보면 5 개의 레지스터와 몇 개의 1 비트 플래그가 있습니다. 유일한 "전체"레지스터는 프로그램 카운터 (PC)였습니다.이 레지스터는 다음에 실행될 명령을 가리 킵니다. 다른 하나는 누산기 (A), 두 개의 "인덱스"레지스터 (X 및 Y) 및 스택 포인터 (SP)를 등록합니다.

서브 루틴을 호출하면 SP가 가리키는 메모리에 PC가 배치되고 SP가 감소합니다. 서브 루틴에서 복귀하면 반대로 작동합니다. 스택에서 다른 값을 밀고 당길 수는 있지만 SP와 관련된 메모리를 참조하기는 어렵 기 때문에 재진입 서브 루틴을 작성하는 것이 어려웠습니다. 우리가 느낀 것처럼 언제든지 서브 루틴을 호출하는 것은 당연한 것으로 여겨졌지만이 아키텍처에서는 그리 일반적이지 않았습니다. 종종, 매개 변수와 서브 루틴 리턴 주소가 별도로 유지되도록 별도의 "스택"이 작성됩니다.

6502, 6800 에서 영감을 얻은 프로세서를 살펴보면 SP만큼 넓은 추가 레지스터 인 인덱스 레지스터 (IX)가 있으며 SP에서 값을받을 수 있습니다.

머신에서 재진입 서브 루틴 호출은 스택의 매개 변수 푸시, PC 푸시, PC를 새 주소로 변경 한 다음 서브 루틴이 로컬 변수를 스택으로 푸시하는 것으로 구성 됩니다. 로컬 변수 및 매개 변수의 수는 알려져 있으므로 스택을 기준으로 주소 지정을 수행 할 수 있습니다. 예를 들어, 두 개의 매개 변수를 수신하고 두 개의 로컬 변수를 갖는 함수는 다음과 같습니다.

SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1

모든 임시 공간이 스택에 있기 때문에 여러 번 호출 할 수 있습니다.

8080 의 간접 레지스터, HL에 터지는 후 스택에 SP를 밀고에 의해, 6800과 비슷한 뭔가를 할 수 TRS-80과 CP / M 기반 마이크로 컴퓨터의 호스트에 사용.

이것은 구현하는 매우 일반적인 방법이며 더 현대적인 프로세서에서 더 많은 지원을 얻었습니다.베이스 포인터를 사용하면 모든 로컬 변수를 쉽게 반환하기 전에 덤프 할 수 있습니다.

문제는 어떻게 당신이 무엇을 반환 합니까? 프로세서 레지스터는 초기에는 그리 많지 않았으며, 어떤 레지스터를 처리 할 메모리를 찾기 위해 일부를 사용해야하는 경우가 종종있었습니다. 스택에 물건을 반환하는 것은 복잡합니다. 모든 것을 팝하고 PC를 저장하고 반환 매개 변수 (그 동안 저장되어 있습니까?)를 누른 다음 PC를 다시 밀고 돌아와야합니다.

따라서 일반적으로 수행 된 작업은 하나의 레지스터 를 반환 값 으로 예약 하는 것이 었습니다 . 호출 코드는 반환 값이 특정 레지스터에있을 것임을 알았습니다.이 값은 저장되거나 사용될 때까지 보존되어야합니다.

여러 반환 값을 허용하는 언어를 살펴 보겠습니다. Forth. Forth가하는 일은 별도의 리턴 스택 (RP)과 데이터 스택 (SP)을 유지하는 것이므로 모든 기능은 모든 매개 변수를 팝하고 리턴 값을 스택에 남겨 두어야합니다. 반환 스택은 분리되어 있었기 때문에 방해가되지 않았습니다.

컴퓨터를 사용한 첫 6 개월 동안 어셈블리 언어와 Forth를 배운 사람이라면 여러 반환 값이 전적으로 정상적인 것처럼 보입니다. /mod정수 나누기 나머지 를 반환하는 Forth와 같은 연산자 는 분명해 보입니다. 반면에, 초기 경험이 C 인 사람이 그 개념이 이상하다는 것을 쉽게 알 수 있습니다. 그것은 "기능"이 무엇인지에 대한 그들의 뿌리 깊은 기대에 어긋납니다.

수학은 ... 수학 수업에서 기능을하기 전에 컴퓨터를 프로그래밍하고있었습니다. 이 이 아닌 전체 부분이있다, 다시, CS 및 수학에 의해 영향을 받는다 프로그래밍 언어의 전체 부분은 있지만.

따라서 수학이 초기 언어 설계에 영향을 미치는 요인, 하드웨어 제약으로 인해 쉽게 구현되는 내용 및 인기있는 언어가 하드웨어의 진화 방식에 영향을 미치는 요인이 합류했습니다 (Lisp 기계 및 Forth 기계 프로세서는이 프로세스에서로드 킬이었습니다).


@gnat "필수 품질 제공"에서와 같이 "알림"을 사용하는 것은 의도적 인 것입니다.
Daniel C. Sobral

이것에 대해 강하게 느끼면 자유롭게 롤백하십시오. 내 독서에 따라 영향력이 여기에 약간 더 맞는 : "영향을 ... 중요한 방법으로"
모기

1
+1 지적했듯이, 현대 CPU의 풍부한 레지스터 세트 (x64 abi와 같은 많은 ABI에서 사용됨)에 비해 초기 CPU의 희소 레지스터 수는 게임 체인저 일 수 있으며 이전에는 1 값만 반환 해야하는 강력한 이유 일 수 있습니다 요즘은 역사적인 이유 일 수 있습니다.
BitTickler

초기 8 비트 마이크로 CPU가 언어 설계와 모든 아키텍처에서 C 또는 Fortran 호출 규칙에 필요한 것으로 가정하는 데 많은 영향을 미쳤다고 확신하지 않습니다. Fortran은 배열 인수 (필수적으로 포인터)를 전달할 수 있다고 가정합니다. 6502와 같은 컴퓨터에서 일반적인 Fortran에 대한 주요 구현 문제가 있습니다. 왜냐하면 답변과 C-Z80 컴파일러 에서 왜 코드가 좋지 않습니까? retrocomputing.SE에.
Peter Cordes

C와 마찬가지로 Fortran은 임의의 수의 인수를 전달할 수 있고 임의의 수의 로컬에 액세스 할 수 있다고 가정합니까? 방금 설명했듯이 재진입을 포기하지 않으면 스택 상대 주소 지정이 중요하지 않기 때문에 6502에서 쉽게 수행 할 수 없습니다 . 정적 저장소에 팝업을 넣을 수 있습니다. 임의의 인수 목록을 전달할 수있는 경우 레지스터에 맞지 않는 반환 값 (예 : 첫 번째 값 초과)에 숨겨진 매개 변수를 추가 할 수 있습니다.
Peter Cordes

7

내가 아는 기능적 언어는 튜플을 사용하여 여러 값을 쉽게 반환 할 수 있습니다 (동적 유형 언어에서는 목록을 사용할 수도 있음). 튜플은 다른 언어로도 지원됩니다.

f :: Int -> (Int, Int)
f x = (x - 1, x + 1)

// Even C++ have tuples - see Boost.Graph for use
std::pair<int, int> f(int x) {
  return std::make_pair(x - 1, x + 1);
}

위의 예에서 f2 정수를 반환하는 함수입니다.

마찬가지로 ML, Haskell, F # 등도 데이터 구조를 반환 할 수 있습니다 (포인터가 대부분의 언어에서 너무 낮은 수준임). 나는 그러한 제한이있는 현대 GP 언어에 대해 들어 본 적이 없습니다.

data MyValue = MyValue Int Int

g :: Int -> MyValue
g x = MyValue (x - 1, x + 1)

마지막으로, out기능 언어에서도 매개 변수를 에뮬레이션 할 수 있습니다 IORef. 대부분의 언어에서 변수를 기본적으로 지원하지 않는 데는 몇 가지 이유가 있습니다.

  • 불명확 한 의미 : 다음 함수가 0 또는 1을 인쇄합니까? 나는 0을 인쇄하는 언어와 1을 인쇄하는 언어를 알고 있습니다. 두 가지 모두 (성능 측면뿐만 아니라 프로그래머의 정신 모델과 일치) 두 가지 이점이 있습니다.

    int x;
    
    int f(out int y) {
      x = 0;
      y = 1;
      printf("%d\n", x);
    }
    f(out x);
    
  • 지역화되지 않은 효과 : 위의 예에서와 같이 긴 사슬을 가질 수 있으며 가장 안쪽의 함수는 전역 상태에 영향을 미친다는 것을 알 수 있습니다. 일반적으로, 기능의 요구 사항이 무엇인지, 그리고 변경이 합법적인지에 대한 추론을 어렵게 만듭니다. 대부분의 최신 패러다임은 효과를 현지화하거나 (OPP의 캡슐화) 부작용 (기능 프로그래밍)을 제거하려고 시도 할 때 해당 패러다임과 충돌합니다.

  • 중복되는 경우 : 튜플이있는 경우 99 %의 out매개 변수 기능 과 100 %의 관용적 사용이 가능합니다. 믹스에 포인터를 추가하면 나머지 1 %를 커버합니다.

튜플, 클래스 또는 out매개 변수 를 사용하여 여러 값을 반환 할 수없는 한 언어의 이름을 지정하는 데 문제가 있습니다 (대부분의 경우 이러한 메서드 중 2 개 이상이 허용됩니다).


+1 기능적 언어가 이것을 우아하고 고통없이 처리하는 방법에 대해 언급했습니다.
Andres F.

1
기술적으로 여전히 단일 값 still : D를 반환합니다 (이 단일 값이 여러 값으로 분해되는 것은 간단합니다).
Thomas Eding

1
실제 "out"의미가있는 매개 변수는 메소드가 정상적으로 종료 될 때 대상에 복사되는 컴파일러 임시로 작동해야합니다. "inout"의미 변수가있는 것은 입력시 전달 된 변수에서로드되고 종료시 다시 쓰여지는 컴파일러 임시로 작동해야합니다. "ref"의미가있는 것은 별명으로 동작해야합니다. C #의 소위 "out"매개 변수는 실제로 "ref"매개 변수이며 그대로 작동합니다.
supercat

1
튜플 "해결 방법"도 무료로 제공되지 않습니다. 최적화 기회를 차단합니다. CPU 레지스터에 N 개의 리턴 값을 리턴 할 수있는 ABI가 존재하면 컴파일러는 튜플 인스턴스를 작성하고 구성하는 대신 실제로 최적화 할 수 있습니다.
BitTickler

1
@BitTickler ABI를 제어하는 ​​경우 레지스터가 전달할 첫 번째 n 개의 필드 필드를 반환하는 것을 막을 수있는 것은 없습니다.
Maciej Piechotka

6

나는 같은 때문이라고 생각합니다 (a + b[i]) * c.

식은 "단수"값으로 구성됩니다. 따라서 단일 값을 반환하는 함수는 위에 표시된 네 가지 변수 대신 식에 직접 사용될 수 있습니다. 다중 출력 함수는 표현식 에서 적어도 다소 어색합니다.

나는 개인적으로이이라고 생각 단수 반환 값에 대한 특별 것. 식에 사용하려는 여러 반환 값을 지정하기위한 구문을 추가하여이 문제를 해결할 수 있지만 모든 사람에게 간결하고 친숙한 오래된 오래된 수학 표기법보다 더 어색합니다.


4

구문을 약간 복잡하게 만들지 만 구현 수준에서 허용하지 않는 좋은 이유는 없습니다. 다른 응답 중 일부와 달리, 가능한 경우 여러 값을 반환하면 더 명확하고 효율적인 코드가 생성됩니다. X Y 또는 "성공"부울 값 유용한 값을 반환 할 수있는 빈도를 계산할 수 없습니다 .


3
여러 수익이 더 명확하고 효율적인 코드를 제공하는 예를 제공 할 수 있습니까?
Steven Evers

3
예를 들어, C ++ COM 프로그래밍에서 많은 함수는 하나의 [out]매개 변수를 갖지만 사실상 모든 것은 HRESULT(오류 코드)를 리턴합니다 . 거기에 한 쌍을 얻는 것이 매우 실용적입니다. 파이썬과 같은 튜플을 잘 지원하는 언어에서 이것은 내가 본 많은 코드에서 사용됩니다.
Felix Dombek

일부 언어에서는 X 및 Y 좌표를 가진 벡터 를 반환하며 , 유용한 값을 반환하면 예외로 "성공"으로 계산되어 해당 유용한 값을 전달하여 실패에 사용됩니다.
doppelgreener

3
많은 시간을 분명하지 않은 방식으로 리턴 값으로 인코딩 정보를 작성합니다. 즉; 음수 값은 오류 코드이고 양수 값은 결과입니다. 수다. 해시 테이블에 액세스하면 항목을 찾았는지 표시하고 항목을 반환하는 것이 항상 지저분합니다.
ddyer

@SteveEvers Matlab sort함수는 일반적으로 배열을 정렬합니다 sorted_array = sort(array). 때때로 나는 또한 상응하는 지수가 필요합니다 : [sorted_array, indices] = sort(array). 때로는 색인 원합니다 : [~, indices]= sort (array) . The function sort`는 실제로 얼마나 많은 출력 인수가 필요한지 알 수 있으므로 1과 비교하여 2 개의 출력에 추가 작업이 필요한 경우 필요한 경우에만 해당 출력을 계산할 수 있습니다.
gerrit

2

함수가 지원되는 대부분의 언어에서 해당 유형의 변수를 사용할 수있는 곳이면 어디에서나 함수 호출을 사용할 수 있습니다.

x = n + sqrt(y);

함수가 둘 이상의 값을 반환하면 작동하지 않습니다. 파이썬과 같이 동적으로 유형이 지정된 언어를 사용하면이 작업을 수행 할 수 있지만 대부분의 경우 방정식 중간에 튜플과 관련이있는 것을 해결할 수 없다면 런타임 오류가 발생합니다.


5
부적절한 기능을 사용하지 마십시오. 이것은 값을 반환하지 않거나 숫자가 아닌 값을 반환하는 함수에 의해 발생하는 "문제"와 다르지 않습니다.
ddyer

3
여러 반환 값 (예 : SciLab)을 제공하는 언어에서는 첫 번째 반환 값이 특권을 가지며 하나의 값만 필요한 경우에 사용됩니다. 따라서 실제 문제는 없습니다.
광자

파이썬의 튜플 포장 풀기와 같이 그렇지 않은 경우에도 원하는 것을 선택할 수 있습니다.foo()[0]
Izkata

정확히 함수가 2 개의 값을 반환하면 반환 유형은 단일 값이 아닌 2 개의 값입니다. 프로그래밍 언어는 당신의 마음을 읽지 말아야합니다.
Mark E. Haase

1

하비의 답을 바탕으로하고 싶습니다. 나는 원래이 뉴스 기술 사이트 (arstechnica) 에서이 질문을 발견 했으며이 질문의 핵심에 실제로 답변하고 다른 모든 답변 (하비 제외)에서 부족하다는 놀라운 설명을 발견했습니다.

함수에서 단일 리턴의 원점은 기계 코드에 있습니다. 기계 코드 레벨에서 함수는 A (누적 기) 레지스터의 값을 리턴 할 수 있습니다. 다른 반환 값은 스택에 있습니다.

두 개의 반환 값을 지원하는 언어는 하나를 반환하는 기계 코드로 컴파일하여 두 번째 반환 값을 스택에 넣습니다. 즉, 두 번째 반환 값은 어쨌든 out 매개 변수로 끝납니다.

할당이 한 번에 하나의 변수 인 이유를 묻는 것과 같습니다. 예를 들어 a, b = 1, 2를 허용하는 언어를 사용할 수 있습니다. 그러나 머신 코드 레벨은 a = 1이되고 b = 2가됩니다.

프로그래밍 언어 구조가 코드를 컴파일하고 실행할 때 실제로 어떤 일이 일어날 지에 대한 몇 가지 근거가 있습니다.


C와 같은 저수준 언어가 1 급 기능으로 다중 리턴 값을 지원하는 경우 C 호출 규칙에는 다중 리턴 값 레지스터가 포함됩니다. x86에서 정수 / 포인터 함수 인수를 전달하는 데 최대 6 개의 레지스터가 사용됩니다. 64 시스템 V ABI. (실제로 x86-64 SysV는 최대 16 바이트의 RDX : RAX 레지스터 쌍으로 압축 된 구조체를 반환합니다. 구조체가 방금로드되어 저장 될 경우에는 좋지만 별도의 정규식에 별도의 멤버가있는 경우와 비교하여 별도의 언 패킹 비용이 발생합니다. 64 비트보다 좁을 경우)
Peter Cordes

명백한 관습은 RAX, arg-passing regs입니다. (RDI, RSI, RDX, RCX, R8, R9). 또는 Windows x64 규칙 인 RCX, RDX, R8, R9에서. 그러나 C에는 기본적으로 여러 반환 값이 없으므로 C ABI / 호출 규칙은 넓은 정수와 일부 구조체에 대해 여러 반환 레지스터 만 지정합니다. 참조 ARM 짋 아키텍처 용 프로 시저 호출 표준 : 2 별도하지만 관련 반환 값을 ARM에 2 개 반환 값을 받기위한 효율적인 ASM을 만들기 위해 컴파일러를 얻기 위해 다양한 INT를 사용하는 예를 들어.
Peter Cordes

-1

수학으로 시작했습니다. "수식 번역"으로 명명 된 FORTRAN은 최초의 컴파일러였습니다. FORTRAN은 물리 / 수학 / 엔지니어링을 지향하고있었습니다.

거의 오래된 COBOL에는 명시적인 반환 값이 없었습니다. 서브 루틴은 거의 없었다. 그 이후로 대부분 관성 상태였습니다.

예를 들어, Go 에는 반환 값이 여러 개 있으며 결과는 "out"매개 변수를 사용하는 것보다 깨끗하고 모호하지 않습니다. 약간만 사용한 후에는 매우 자연스럽고 효율적입니다. 모든 새 언어에 대해 여러 개의 반환 값을 고려하는 것이 좋습니다. 아마도 오래된 언어 일 수도 있습니다.


4
이것은 묻는 질문에 대답하지 않습니다
gnat

@ gnat 나에게 대답합니다. OTOH는 이미 그것을 이해하기 위해 약간의 배경이 필요하며, 그 사람은 질문을하지 않을 것입니다 ...
Netch

@Netch는 "FORTRAN ...은 최초의 컴파일러였다" 와 같은 문장 들이 완전히 혼란 스럽다는 것을 알기 위해 많은 배경을 필요로하지 않는다 . 잘못이 아닙니다. 이 "답변"의 나머지 부분과 마찬가지로
gnat

link 는 이전에 컴파일러에 대한 시도가 있었지만 "IBM의 John Backus가 이끄는 FORTRAN 팀은 일반적으로 1957 년에 최초의 완전한 컴파일러를 도입 한 것으로 알려져 있습니다"라고 말합니다. 질문은 왜 단 하나인가? 내가 말했듯이. 그것은 주로 수학과 관성이었다. "함수"라는 용어의 수학적 정의에는 정확히 하나의 결과 값이 필요합니다. 익숙한 형태였습니다.
RickyS

-2

프로세서 기계 명령어에서 함수 호출이 수행되는 방식과 모든 프로그래밍 언어가 기계 코드에서 파생된다는 사실과 더 관련이있을 것입니다 (예 : C-> Assembly-> Machine).

프로세서가 함수 호출을 수행하는 방법

첫 번째 프로그램은 기계 코드로 작성된 후 나중에 조립되었습니다. 프로세서는 모든 현재 레지스터의 사본을 스택으로 푸시하여 함수 호출을 지원했습니다. 함수에서 복귀하면 스택에서 저장된 레지스터 세트가 나타납니다. 일반적으로 하나의 레지스터는 반환 기능이 값을 반환 할 수 있도록 그대로 유지됩니다.

이제 프로세서가 이런 식으로 디자인 된 이유는 리소스 제약 문제 일 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.