openlang

콜라츠에는 partial def가 정직한 답이다

Lean은 재귀 def를 정의로 받아들이기 전에 그것이 끝난다는 것을 알아야 한다. 구조적 재귀는 알아서 찾는다. 아니면 정초 재귀를 시도하고, 사람은 termination_by로 측도를 지정하고 decreasing_by로 호출마다 그 값이 줄어든다는 것을 증명해서 돕는다.

콜라츠 단계 수에는 알려진 측도가 없다. 모든 입력에서 끝난다는 것을 증명한 사람이 없다. 그래서 partial def로 쓴다. Lean은 이것을 컴파일하고 실행한다. 반환 타입에 값이 존재해야 한다는 조건이 붙고, 증명 안에서는 정의가 불투명하다. 계산은 된다. 펼쳐서 추론하는 것은 안 된다.

나는 이 거래가 마음에 든다. 여기서 키워드는 사실을 말하는 사양이다. 전체 함수인 def는 모든 입력에서 반환한다고 말하고, 검사기는 그 말에 책임을 지운다. partial은 그런 말을 하지 않는다.

항상 답을 내는 검사기라면 실제로 끝나는 함수 일부도 거절할 수밖에 없다. 괜찮다. 그 거절의 비용은 정직한 키워드 하나이고, 프로그램은 여전히 돈다.

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    Idris 2에는 이름표가 하나 더 있는데, 저는 그걸 제일 자주 써요. total은 말씀하신 정직한 def이고 partial은 partial def예요. 그 사이에 covering이 있어요. 모든 입력에 맞는 절이 있다는 뜻이고, 끝난다는 약속은 하지 않아요. 콜라츠 단계 수라면 이게 쓸모 있는 주장이에요. 실제로 저를 괴롭히는 건 무한 루프보다 빠뜨린 경우거든요. 그리고 파일 맨 위에 %default total을 두면 나머지 함수 전부의 부담이 뒤집혀요. 거의 산술뿐인 모듈에서는 꽤 편해요.

  2. 고유 링크

    그냥 화살표는 covering보다도 적게 주장해요. 같은 함수의 OCaml 타입은 int -> int이고, let rec은 실행 중에 무슨 일을 하든 받아들여져요. 모든 패턴을 다룬다는 약속조차 없어요. 빠진 패턴은 경고일 뿐이니까요. 주장이 작은 건 괜찮아요. 화살표를 그 이상으로 읽지만 않으면요.

  3. 고유 링크

    여기서는 두 종류의 프로그램을 나눠 볼 만하다. 콜라츠 단계 수는 숫자를 돌려줘야 하니 종료가 맞는 질문이다. REPL이나 서버는 절대 돌아오면 안 되고, 원하는 성질은 루프의 한 바퀴가 매번 끝나며 루프 자체는 상수 공간에서 돈다는 것이다.

    R7RS는 두 번째를 규칙 하나로 해결한다. 3.5절은 올바른 꼬리 재귀를 요구한다. 꼬리 위치에서 자기 자신을 부르는 프로시저는 그대로 루프가 되고, 스택도 자라지 않고 특별한 문법도 필요 없다. 그래서 교육용 Scheme에서는 서버를 평범한 재귀 다섯 줄로 보여 줄 수 있다.

    전체성 검사기는 그 루프에 대해 할 말이 없다. partial도 마찬가지다. 빠진 단어는 생산성이다.

같은 질문의 다른 갈래

  1. 탐색 예산 소진은 답의 부재가 아닙니다

    let loop = loop in if loop then Just 1 else Nothing의 타입은 Maybe Int이지만, 이 식은 Just도 Nothing도 아닙니다. 어느 쪽인지 가리려면 조건이 필요한데, 그 조건은…

    답글 1haskell