재귀는 그 자체로 기능입니까?


116

... 아니면 그냥 연습입니까?

저는 교수님과의 논쟁 때문에 이것을 묻고 있습니다. 저는 수업에서 재귀를 다루지 않았기 때문에 함수를 재귀 적으로 호출 한 것에 대한 크레딧을 잃었고, 제 주장은 학습 return과 방법을 통해 암묵적으로 배웠다는 것 입니다.

나는 누군가가 확실한 답을 가지고 있다고 생각하기 때문에 여기에서 묻는 것입니다.

예를 들어 다음 두 방법의 차이점은 무엇입니까?

public static void a() {
    return a();
    }

public static void b() {
    return a();
    }

" a영원히 계속"(실제 프로그램에서는 잘못된 입력이 제공 될 때 사용자에게 다시 메시지를 표시하는 데 올바르게 사용됨) 외에 a와 사이에 근본적인 차이점이 b있습니까? 최적화되지 않은 컴파일러에게는 어떻게 다르게 처리됩니까?

궁극적으로는 배우는 의해 여부에 온다 return a()에서 b우리는 또한 배운 그 때문에 그 return a()에서 a. 우리가?


24
훌륭한 토론. 교수님에게 이렇게 설명 하셨는지 궁금합니다. 당신이 그랬다면 그는 당신에게 잃어버린 신용을 주어야한다고 생각합니다.
Michael Yaworski

57
재귀는 컴퓨터 과학 만의 개념도 아닙니다. 피보나치 함수, 계승 연산자 및 수학 (및 가능한 다른 필드)의 다른 많은 것들이 재귀 적으로 표현됩니다 (또는 적어도 가능할 수 있음). 교수님은 당신이 이것들에 대해서도 잘 모르라고 요구합니까?
Theodoros Chatzigiannakis

34
교수는 그에게 문제를 해결하는 우아한 방법을 제시하거나 상자에서 벗어난 사고에 대해 그에게 추가 점수를 주어야합니다.

11
임무는 무엇입니까? 이것은 제가 자주 궁금해했던 문제입니다. 프로그래밍 과제를 제출할 때 무엇이 ​​표시되는지, 문제를 해결하는 능력 또는 배운 것을 사용하는 능력입니다. 이 두 가지는 반드시 동일하지는 않습니다.
Leon

35
FWIW, 옳을 때까지 입력을 요청하는 것은 재귀를 사용하기에 좋은 곳이 아니며 스택을 오버플로하기가 너무 쉽습니다. 이 특별한 경우에는 a() { do { good = prompt(); } while (!good); }.
Kevin

답변:


113

특정 질문에 답하려면 : 아니요, 언어 학습의 관점에서 재귀는 기능이 아닙니다. 만약 당신의 교수가 정말로 그가 아직 가르치지 않은 "기능"을 사용한다고 표시했다면 그것은 잘못된 것입니다.

줄 사이를 읽으면서, 한 가지 가능성은 재귀를 사용함으로써 그의 과정에 대한 학습 결과로 여겨지는 기능을 사용하지 않았을 것입니다. 예를 들어, 반복을 전혀 사용하지 않았거나 및 for을 모두 사용 하는 대신 루프 만 사용했을 수 있습니다 . 과제는 특정 작업을 수행하는 능력을 테스트하는 것을 목표로하는 것이 일반적이며,이를 피하면 교수가 해당 기능에 대해 따로 설정 한 점수를 부여 할 수 없습니다. 그러나 그것이 정말로 당신의 잃어버린 점수의 원인이라면, 교수는 이것을 자신의 학습 경험으로 받아 들여야합니다. 특정 학습 결과를 보여주는 것이 과제의 기준 중 하나라면 학생들에게 명확하게 설명해야합니다 .forwhile

그렇게 말하면서 반복이 여기서 재귀보다 더 나은 선택이라는 다른 대부분의 의견과 답변에 동의합니다. 몇 가지 이유가 있으며 다른 사람들이 어느 정도 그들에게 접촉했지만 그들이 그 뒤에있는 생각을 완전히 설명했는지 확신 할 수 없습니다.

스택 오버플로

더 분명한 것은 스택 오버플로 오류가 발생할 위험이 있다는 것입니다. 현실적으로, 사용자가 실제로 스택 오버플로를 트리거하기 위해 잘못된 입력을 여러 번 제공해야하기 때문에 작성한 메서드가 실제로 하나로 이어질 가능성은 거의 없습니다.

그러나 명심해야 할 한 가지는 메서드 자체뿐만 아니라 호출 체인에서 더 높거나 낮은 다른 메서드가 스택에 있다는 것입니다. 이 때문에 사용 가능한 스택 공간을 부담없이 차지하는 것은 모든 방법에 대해 매우 무례한 일입니다. 아무도 코드를 작성할 때마다 빈 스택 공간에 대해 계속 걱정할 필요가 없습니다. 다른 코드가 불필요하게 많은 공간을 사용할 위험이 있기 때문입니다.

이것은 추상화라고하는 소프트웨어 설계의보다 일반적인 원칙의 일부입니다. 기본적으로을 (를) 호출 할 때 DoThing()주의해야 할 것은 Thing이 완료된 것입니다. 어떻게 수행 되는지 에 대한 구현 세부 사항에 대해 걱정할 필요가 없습니다 . 그러나 스택을 탐욕스럽게 사용하면이 원칙이 깨집니다. 모든 코드는 콜 체인의 다른 곳에서 코드에 의해 스택이 얼마나 안전하게 남았는지에 대해 걱정해야하기 때문입니다.

가독성

다른 이유는 가독성입니다. 코드가 바라는 이상은 사람이 읽을 수있는 문서가되는 것입니다. 여기서 각 줄은 자신이하는 일을 간단히 설명합니다. 다음 두 가지 접근 방식을 사용하십시오.

private int getInput() {
    int input;
    do {
        input = promptForInput();
    } while (!inputIsValid(input))
    return input;
}

private int getInput() {
    int input = promptForInput();
    if(inputIsValid(input)) {
        return input;
    }
    return getInput();
}

예, 둘 다 작동하며 예, 둘 다 이해하기 쉽습니다. 그러나 두 가지 접근 방식을 영어로 어떻게 설명 할 수 있습니까? 나는 그것이 다음과 같을 것이라고 생각합니다.

입력이 유효 할 때까지 입력을 요청한 다음 반환합니다.

입력을 요청하고 입력이 유효하면 반환하고, 그렇지 않으면 입력을 받고 그 결과를 대신 반환합니다.

아마도 후자에 대해 약간 덜 투박한 표현을 생각할 수 있지만 항상 첫 번째가 개념적으로 실제로 수행하려는 작업에 대한보다 정확한 설명이 될 것임을 항상 발견 할 것입니다. 이것은 재귀가 항상 가독성 이 낮다 는 것을 의미하지는 않습니다 . 트리 순회와 같이 빛나는 상황의 경우 재귀와 다른 접근 방식 사이에서 동일한 종류의 나란히 분석을 수행 할 수 있으며 재귀가 한 줄씩 더 명확하게 설명하는 코드를 제공한다는 사실을 거의 확실하게 찾을 수 있습니다.

분리해서 두 가지 모두 작은 점입니다. 이것이 실제로 스택 오버플로로 이어질 가능성은 매우 낮으며 가독성 향상은 미미합니다. 그러나 모든 프로그램은 이러한 작은 결정의 모음이 될 것이므로 격리되어 있어도 그다지 중요하지 않더라도 올바른 결정의 원칙을 배우는 것이 중요합니다.


8
재귀가 기능이 아니라는 주장을 확장 할 수 있습니까? 모든 컴파일러가 반드시 지원하는 것은 아니기 때문에 내 대답에서 주장했습니다.
Harry Johnston

5
모든 언어가 반드시 재귀를 지원하는 것은 아니므로 올바른 컴파일러를 선택하는 문제 일 필요는 없지만 "기능"은 본질적으로 모호한 설명이므로 충분히 공정하다고 말하는 것이 옳습니다. 두 번째 요점은 먼저 기계 코드 프로그래밍에 대한 배경 지식없이 프로그래밍을 배우는 사람의 관점에서도 공정합니다. :-)
Harry Johnston

2
'가독성'문제는 구문의 문제입니다. 재귀에 대해 본질적으로 "읽을 수없는"것은 없습니다. 사실, 귀납법은 루프, 목록 및 시퀀스 등과 같은 귀납적 데이터 구조를 표현하는 가장 쉬운 방법입니다. 그리고 대부분의 데이터 구조는 귀납적입니다.
nomen

6
나는 당신이 당신의 문구로 덱을 쌓아 놓은 것 같습니다. 반복 버전을 기능적으로 또는 그 반대로 설명했습니다. 두 가지 모두에 대한보다 공정한 설명은“입력을 요청하겠습니다. 입력이 유효하지 않으면 유효한 입력을받을 때까지 계속 프롬프트를 반복합니다. 그럼 돌려 드리겠습니다.” vs“입력을 요청합니다. 입력이 유효하면 반환하겠습니다. 그렇지 않으면 do-over 결과를 반환합니다.” (우리 아이들은 유치원에있을 때 do-over의 기능적 개념을 이해 했으므로 여기에서 재귀 적 개념의 합법적 인 영어 요약이라고 생각합니다.)
pjs

2
@HarryJohnston 재귀에 대한 지원 부족은 새로운 기능이 부족하기보다는 기존 기능에 대한 예외 입니다. 특히, 재귀 사실이 아니다이 질문에, "새로운 기능"수단 "우리가 아직 배운하지 않은 유용 행위가 존재"의 맥락에서이 기능의 논리적 연장이기 때문에 되었다 즉 (가르침은 절차가 포함되어 있는지, 지시 및 절차 호출은 지시입니다). 마치 교수가 학생 덧셈을 가르치고 "우리는 곱셈을 다루지 않았기 때문에"같은 값을 두 번 이상 더했다고 그를 꾸짖는 것과 같습니다.
nmclean 2014 년

48

메타 질문이 아니라 문자적인 질문에 답하기 위해 : 재귀 모든 컴파일러 및 / 또는 언어가 반드시 허용하는 것은 아니라는 점에서 기능입니다. 실제로는 모든 (일반적인) 최신 컴파일러와 확실히 모든 Java 컴파일러에서 예상됩니다! -그러나 그것은 보편적으로 사실 이 아닙니다 .

재귀가 지원되지 않는 이유에 대한 인위적인 예로서 함수의 반환 주소를 정적 위치에 저장하는 컴파일러를 고려하십시오. 예를 들어 스택이없는 마이크로 프로세서 용 컴파일러의 경우 일 수 있습니다.

이러한 컴파일러의 경우 다음과 같은 함수를 호출하면

a();

그것은 다음과 같이 구현됩니다.

move the address of label 1 to variable return_from_a
jump to label function_a
label 1

그리고 a ()의 정의,

function a()
{
   var1 = 5;
   return;
}

다음과 같이 구현됩니다.

label function_a
move 5 to variable var1
jump to the address stored in variable return_from_a

a()그러한 컴파일러에서 재귀 적 으로 호출하려고 할 때의 문제 가 분명하기를 바랍니다. 반환 주소가 덮어 써 졌기 때문에 컴파일러는 더 이상 외부 호출에서 반환하는 방법을 알지 못합니다.

재귀를 지원하지 않고 실제로 사용한 컴파일러 (70 년대 후반 또는 80 년대 초반)의 경우 문제는 그보다 약간 더 미묘했습니다. 반환 주소는 현대 컴파일러와 마찬가지로 스택에 저장되지만 로컬 변수는 '티. (이론적으로 이것은 비 정적 지역 변수가없는 함수에 대해 재귀가 가능하다는 것을 의미해야하지만 컴파일러가이를 명시 적으로 지원했는지 여부를 기억하지 못합니다. 어떤 이유로 암시 적 지역 ​​변수가 필요할 수 있습니다.)

앞으로 모든 스레드에 대해 스택을 제공 할 필요가없는 것이 유리할 수 있고 컴파일러가 루프로 리팩토링 할 수있는 경우에만 재귀가 허용되는 경우와 같은 고도의 병렬 시스템과 같은 특수 시나리오를 상상할 수 있습니다. (물론 위에서 논의한 기본 컴파일러는 코드 리팩토링과 같은 복잡한 작업을 수행 할 수 없었습니다.)


예를 들어 C 전처리 기는 매크로에서 재귀를 지원하지 않습니다. 매크로 정의는 함수와 유사하게 작동하지만 재귀 적으로 호출 할 수는 없습니다.
sth

7
당신의 "논의 된 예제"가 전부 인위적인 것은 아닙니다. Fortran 77 표준은 함수가 자신을 재귀 적으로 호출하는 것을 허용하지 않았습니다. 그 이유는 당신이 설명하는 것입니다. (함수가 완료되었을 때 이동할 주소가 함수 코드 자체의 끝에 저장되어 있거나이 배열과 동일한 내용이 저장되어 있다고 생각합니다.) 이에 대한 내용은 여기 를 참조 하십시오 .
알렉시스

5
셰이더 언어 또는 GPGPU 언어 (예 : GLSL, Cg, OpenCL C)는 예를 들어 재귀를 지원하지 않습니다. 지금까지 "모든 언어가 지원하는 것은 아닙니다"라는 주장은 확실히 유효합니다. 재귀 스택의 동등 물 (이것은 반드시 필요하지 추정 스택 하지만 점포 리턴 어드레스와 기능 프레임의 수단이있을 필요가 ).
Damon

내가 1970 년대 초에 작업 한 포트란 컴파일러에는 콜 스택이 없었습니다. 각 서브 루틴 또는 함수에는 반환 주소, 매개 변수 및 자체 변수에 대한 정적 메모리 영역이 있습니다.
Patricia Shanahan 2014 년

2
일부 버전의 Turbo Pascal조차 기본적으로 재귀를 비활성화했으며이를 활성화하려면 컴파일러 지시문을 설정해야했습니다.
dan04 2014 년

17

선생님은 당신이 공부했는지 여부를 알고 싶어합니다. 분명히 당신은 그가 당신에게 가르쳐 준 방식 ( 좋은 방법 , 반복) 으로 문제를 해결하지 못했고 , 따라서 당신이하지 않았다고 생각합니다. 저는 모두 창의적인 해결책을 원하지만이 경우에는 다른 이유로 선생님과 동의해야합니다
. 사용자가 잘못된 입력을 너무 많이 제공하면 (즉, Enter 키를 길게 눌러) 스택 오버플로 예외가 발생하고 솔루션이 충돌합니다. 또한 반복 솔루션이 더 효율적이고 유지 관리하기 쉽습니다. 그것이 당신의 선생님이 당신에게 주었어야하는 이유라고 생각합니다.


2
우리는이 작업을 특정한 방식으로 수행하라는 지시를받지 않았습니다. 우리는 반복 만이 아니라 방법에 대해서도 배웠습니다. 또한 개인적 선호도에 따라 어떤 것이 더 읽기 쉬운 지 남겨두고 싶습니다. 나에게 잘 어울리는 것을 선택했습니다. 재귀가 기능이며 그 자체로는 아직 발견되지 않은 것 같지만 SO 오류는 나에게 새로운 것입니다.

3
"개인 취향에 따라 어느 쪽이 더 읽기 쉬운 지 남겨두고 싶습니다." 동의합니다. 재귀는 Java 기능이 아닙니다. 이것들 은.
마이크

2
@Vality : 테일 콜 제거? 일부 JVM은이를 수행 할 수 있지만 예외에 대한 스택 추적도 유지해야합니다. 꼬리 호출 제거를 허용하면 순진하게 생성 된 스택 추적이 무효화 될 수 있으므로 일부 JVM은 이러한 이유로 TCE를 수행하지 않습니다.
icktoofay

5
어느 쪽이든 최적화에 의존하여 손상된 코드를 덜 깨뜨리는 것은 매우 나쁜 형태입니다.
cHao

7
+1, Ubuntu에서 최근 사용자가 Enter 버튼을 계속 누르면 로그인 화면이 깨 졌음을 확인하십시오. XBox에서도 마찬가지입니다
Sebastian

13

"우리가 수업에서 재귀를 다루지 않았기 때문에"점수를 차감하는 것은 끔찍합니다. B로 돌아가는 함수 C를 호출하는 함수 B를 호출하는 함수 A를 호출하는 방법을 배운 경우, 호출자에게 다시 돌아가는 A로 돌아가는 함수 C를 호출하고 교사는 이들이 다른 함수 여야한다는 것을 명시 적으로 말하지 않았습니다. 예를 들어 이전 FORTRAN 버전의 경우) A, B 및 C가 모두 동일한 기능이 될 수는 없습니다.

다른 한편으로, 우리는 당신의 특정한 경우에 재귀를 사용하는 것이 정말로 옳은 일인지 결정하기 위해 실제 코드를 봐야합니다. 세부 사항은 많지 않지만 잘못 들립니다.


10

당신이 질문 한 구체적인 질문과 관련하여 많은 관점이 있지만 제가 말할 수있는 것은 언어 학습의 관점에서 볼 때 재귀는 그 자체로 기능이 아니라는 것입니다. 만약 당신의 교수가 정말로 그가 아직 가르치지 않은 "특징"을 사용했다는 표시를했다면 그것은 틀렸지 만 제가 말했듯이, 실제로 교수가 점수를 감점 할 때 옳다고 여기는 다른 관점이 있습니다.

귀하의 질문에서 추론 할 수있는 것에서 입력 실패의 경우 재귀 함수를 사용하여 입력을 요청하는 것은 모든 재귀 함수 호출이 스택으로 푸시되기 때문에 좋은 습관이 아닙니다. 이 재귀는 사용자 입력에 의해 구동되기 때문에 무한 재귀 함수를 가질 수 있으며 따라서 StackOverflow가 발생합니다.

귀하의 질문에서 언급 한 두 가지 예는 그들이하는 일의 의미에서 차이가 없습니다 (그러나 다른 방식으로 다릅니다)-두 경우 모두 반환 주소와 모든 메서드 정보가 스택에로드됩니다. 재귀의 경우 반환 주소는 단순히 메서드 호출 바로 뒤의 줄입니다 (물론 코드 자체에서 볼 수있는 것과 정확히 일치하는 것이 아니라 컴파일러가 만든 코드에서). Java, C 및 Python에서 재귀는 새 스택 프레임을 할당해야하므로 반복 (일반)에 비해 상당히 비쌉니다. 입력이 너무 여러 번 유효하지 않으면 스택 오버플로 예외가 발생할 수 있습니다.

나는 재귀가 그 자체의 주제로 간주되고 프로그래밍 경험이없는 사람이 재귀를 생각할 가능성이 없기 때문에 교수님이 점수를 감점했다고 생각합니다. (물론 그렇지 않다는 의미는 아니지만 가능성이 낮습니다).

IMHO, 교수님이 점수를 빼서 옳다고 생각합니다. 유효성 검사 부분을 다른 방법으로 쉽게 가져와 다음과 같이 사용할 수 있습니다.

public bool foo() 
{
  validInput = GetInput();
  while(!validInput)
  {
    MessageBox.Show("Wrong Input, please try again!");
    validInput = GetInput();
  }
  return hasWon(x, y, piece);
}

당신이 한 일이 실제로 그런 방식으로 해결 될 수 있다면, 당신이 한 일은 나쁜 습관이었고 피해야합니다.


메서드 자체의 목적은 입력을 확인한 다음 다른 메서드 의 결과를 호출하고 반환하는 것입니다 (이것이 자체를 반환하는 이유입니다). 구체적으로 말하면, Tic-Tac-Toe 게임의 이동이 유효한지 hasWon(x, y, piece)확인한 다음 (영향을받은 행과 열만 확인하기 위해) 반환합니다 .

쉽게 유효성 검사 부분 만 가져 와서 예를 들어 "GetInput"이라는 다른 메서드에 넣은 다음 내 대답에 쓴 것처럼 사용할 수 있습니다. 나는 그것이 어떻게 보일지에 대한 내 대답을 편집했습니다. 물론 GetInput이 필요한 정보를 보유하는 유형을 반환하도록 만들 수 있습니다.
Yonatan Nir

1
Yonatan Nir : 재귀가 언제 나쁜 습관 이었나요? 아마도 JVM은 핫스팟 VM이 바이트 코드 보안으로 인해 최적화 할 수 없기 때문에 폭발 할 것입니다. 코드가 다른 접근 방식을 사용하는 것 외에 다른 점은 무엇입니까?

1
재귀가 항상 나쁜 습관은 아니지만 피할 수 있고 코드를 깨끗하고 유지하기 쉽게 유지한다면 피해야합니다. Java, C 및 Python에서 재귀는 새 스택 프레임을 할당해야하므로 반복 (일반)에 비해 상당히 비쌉니다. 일부 C 컴파일러에서는 컴파일러 플래그를 사용하여이 오버 헤드를 제거 할 수 있습니다.이 오버 헤드는 특정 유형의 재귀 (실제로 특정 유형의 마무리 호출)를 함수 호출 대신 점프로 변환합니다.
Yonatan Nir 2014 년

1
명확하지 않지만 반복을 무한 반복 횟수로 루프를 교체했다면 그것은 나쁘다. Java는 테일 호출 최적화를 보장하지 않으므로 스택 공간이 쉽게 부족할 수 있습니다. Java에서는 제한된 반복 횟수 (일반적으로 전체 데이터 크기에 비해 대수)가 보장되지 않는 한 재귀를 사용하지 마십시오.
hyde 2014 년

6

교수님이 아직 가르치지 않았을 수도 있지만 재귀의 장점과 단점을 배울 준비가 된 것 같습니다.

재귀의 가장 큰 장점은 재귀 알고리즘이 작성하기가 훨씬 쉽고 빠르다는 것입니다.

재귀의 가장 큰 단점은 재귀 알고리즘이 스택 오버플로를 유발할 수 있다는 것입니다. 각 재귀 수준에는 스택에 추가 스택 프레임을 추가해야하기 때문입니다.

스케일링으로 인해 프로그래머의 단위 테스트보다 프로덕션에서 더 많은 수준의 재귀가 발생할 수있는 프로덕션 코드의 경우 일반적으로 단점이 장점보다 크며 실용적 일 때 재귀 코드를 피하는 경우가 많습니다.


1
잠재적으로 위험한 재귀 알고리즘은 항상 명시 적 스택을 사용하도록 사소하게 다시 작성할 수 있습니다. 호출 스택은 결국 스택 일뿐입니다. 이 경우 스택을 사용하도록 솔루션을 다시 작성하면 우스꽝스러워 보일 것입니다. 재귀 적 답변이 그다지 좋지 않다는 추가 증거입니다.
Aaronaught

1
스택 오버플로가 문제가되는 경우 .NET 4.0 또는 모든 기능적 프로그래밍 언어와 같은 테일 호출 최적화를 지원하는 언어 / 런타임을 사용해야합니다
Sebastian

모든 재귀가 꼬리 호출이 아닙니다.
워렌 이슬

6

구체적인 질문과 관련하여 재귀는 기능이므로 예라고 말하고 싶지만 질문을 다시 해석 한 후에. 재귀를 가능하게하는 언어와 컴파일러의 일반적인 디자인 선택이 있으며, 재귀를 전혀 허용하지 않는 Turing-complete 언어가 존재 합니다 . 즉, 재귀는 언어 / 컴파일러 디자인의 특정 선택에 의해 활성화되는 기능입니다.

  • 일류 함수를 지원 하면 최소한의 가정 하에서 재귀가 가능합니다. 예를 들어 Unlambda에서 루프 작성을 참조 하거나 자체 참조, 루프 또는 할당이없는이 둔한 Python 표현식을 참조하십시오.

    >>> map((lambda x: lambda f: x(lambda g: f(lambda v: g(g)(v))))(
    ...   lambda c: c(c))(lambda R: lambda n: 1 if n < 2 else n * R(n - 1)),
    ...   xrange(10))
    [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
    
  • 후기 바인딩 을 사용 하거나 정방향 선언 을 정의 하는 언어 / 컴파일러는 재귀를 가능하게합니다. 예를 들어 Python은 아래 코드를 허용하지만 Turing-complete 시스템에 대한 요구 사항이 아닌 디자인 선택 (늦은 바인딩) 입니다. 상호 재귀 함수는 종종 전방 선언 지원에 의존합니다.

    factorial = lambda n: 1 if n < 2 else n * factorial(n-1)
    
  • 재귀 적으로 정의 된 유형 을 허용하는 정적으로 유형이 지정된 언어재귀 활성화에 기여합니다. Go에서 Y Combinator 구현을 참조하십시오 . 재귀 적으로 정의 된 유형이 없어도 Go에서 재귀를 사용할 수는 있지만 Y 결합자는 특별히 불가능하다고 생각합니다.


1
이것은 내 머리를 폭발 시켰습니다. 특히 Unlambda +1
John Powell

고정 소수점 조합기는 어렵습니다. 함수형 프로그래밍을 배우기로 결심했을 때, Y 조합기를 이해할 때까지 공부하고 다른 유용한 함수를 작성하는 데 적용했습니다. 잠시 시간이 걸렸지 만 그만한 가치가있었습니다.
wberry

5

귀하의 질문에서 추론 할 수있는 것에서 입력 실패시 재귀 함수를 사용하여 입력을 요청하는 것은 좋은 습관이 아닙니다. 왜?

모든 재귀 함수 호출이 스택으로 푸시되기 때문입니다. 이 재귀는 사용자 입력에 의해 구동되기 때문에 무한 재귀 함수를 가질 수 있으므로 StackOverflow :-p

이를 수행하기 위해 비 재귀 루프를 갖는 것이 갈 길입니다.


문제가되는 메서드의 대부분과 메서드 자체의 목적은 다양한 검사를 통해 입력을 확인하는 것입니다. 입력이 정확할 때까지 (지시 된대로) 입력이 유효하지 않으면 프로세스가 다시 시작됩니다.

4
@fay 그러나 입력이 너무 여러 번 유효하지 않으면 StackOverflowError가 발생합니다. 재귀는 더 우아하지만 내 눈에는 보통 (스택으로 인해) 일반 루프보다 문제가 더 많습니다.
Michael Yaworski

1
흥미롭고 좋은 점입니다. 나는 그 오류를 고려하지 않았습니다. 하지만 while(true)동일한 메서드 를 호출하여 동일한 효과를 얻을 수 있습니까? 그렇다면 이것이 재귀의 차이점을 지원한다고 말하지 않을 것입니다.

1
@fay while(true)는 무한 루프입니다. 당신이 break성명서 를 가지고 있지 않는 한 , 당신이 당신의 프로그램을 충돌 시키려고하지 않는 한, 나는 그것의 요점을 보지 못합니다. 내 요점은 동일한 메서드 (재귀)를 호출하면 때때로 StackOverflowError 를 제공하지만 while또는 for루프 를 사용하면 그렇지 않습니다. 문제는 단순히 일반 루프에는 존재하지 않습니다. 내가 당신을 오해했을 수도 있지만, 당신에 대한 나의 대답은 '아니오'입니다.
Michael Yaworski

4
이것은 솔직히 교수가 점수를 떼어 낸 진짜 이유 인 것 같습니다 =) 그는 그것을 잘 설명하지 않았을 수도 있지만, 그렇지 않다면 매우 열악한 스타일로 간주 될 수있는 방식으로 그것을 사용했다고 말하는 것은 유효한 불만입니다 더 심각한 코드에 명백한 결함이 있습니다.
Coriander Salamander 사령관

3

재귀 는 프로그래밍 개념 , 기능 (예 : 반복) 및 연습 입니다. 링크에서 볼 수 있듯이 주제에 전념하는 대규모 연구 영역이 있습니다. 아마도 우리는 이러한 요점을 이해하기 위해 주제에 대해 깊이 들어갈 필요가 없습니다.

기능으로서의 재귀

간단히 말해서 Java는 메서드 (기본적으로 특수 기능)가 자신과 자신이 속한 클래스를 구성하는 다른 메서드에 대한 "지식"을 가질 수 있도록하므로 암시 적으로 지원합니다. 그렇지 않은 언어를 고려하십시오. 해당 메서드의 본문을 작성할 a수는 있지만 그 a안에 호출을 포함 할 수는 없습니다 . 유일한 해결책은 동일한 결과를 얻기 위해 반복을 사용하는 것입니다. 이러한 언어에서는 자신의 존재를 인식하는 함수 (특정 구문 토큰 사용)와 그렇지 않은 함수를 구분해야합니다! 실제로 전체 언어 그룹이 이러한 구분을합니다 ( 예를 들어 LispML 제품군 참조 ). 흥미롭게도 Perl은 익명 함수 (소위lambdas )를 사용하여 자신을 재귀 적으로 호출합니다 (다시 말하지만 전용 구문 사용).

재귀가 없습니까?

재귀의 가능성조차 지원하지 않는 언어의 경우 고정 소수점 조합 자 형태의 또 다른 솔루션이 있지만, 여전히 일류 객체 (즉, 객체가 될 수있는 객체)와 같은 기능을 지원하려면 언어가 필요합니다. 언어 자체 내에서 조작 됨).

연습으로서의 재귀

해당 기능을 언어로 사용할 수 있다고해서 관용적이라는 의미는 아닙니다. Java 8에는 람다식이 포함되어 있으므로 프로그래밍에 대한 기능적 접근 방식을 채택하는 것이 더 쉬워 질 수 있습니다. 그러나 실용적인 고려 사항이 있습니다.

  • 구문은 여전히 ​​재귀 친화적이지 않습니다.
  • 컴파일러는 이러한 실행을 감지하고 최적화 하지 못할 수 있습니다.

결론

운 좋게도 (또는 더 정확하게는 사용하기 쉽도록) Java는 메서드가 기본적으로 자체를 인식하도록 허용하므로 재귀를 지원하므로 실제적인 문제는 아니지만 여전히 이론적 인 문제로 남아 있습니다. 선생님이 구체적으로 설명하고 싶었습니다. 게다가, 최근의 언어 진화에 비추어 볼 때 미래에는 중요한 것으로 바뀔 수 있습니다.

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