一世を風靡した数の主流表現は位置表記です。これは、商と剰余演算の概念と密接に結びついた表現であり、再帰関数の定義から多少見られます。何故ですか?
簡単に脇に置いておきましょう: 位置表記法は、数値の唯一の実行可能な表現ではありません。頻繁に出てくる方法の 1 つは、数値が 0 または数値よりも 1 大きい集計アプローチです。スティックを使用できます。プログラムについて話しているので、data-typeを使用しましょう。
Number :== Zero
| Successor(n) where n is a number
これを「数値はゼロか、別の数値の後継である」と読みます。または、構造化表現 (Racket など) をサポートするスキームでコード化するには、次のように記述できます。
(define-struct Zero ())
(define-struct Successor (n))
たとえば、この表記法で3 つ(Successor (Successor (Successor (Zero)))
を表すと、 になります。(私の記憶が正しければ、この表現はペアノと呼ばれます。)
この種の構造化されたデータ型を扱う関数は、多くの場合、データ型自体と同じ形をしています。つまり、ペアノの表現で機能する関数は次のようになります。
;; a peano-eating-function-template: peano-number -> ???
(define (a-peano-eating-function-template a-num)
(cond [(Zero? a-num)
...]
[(Successor? a-num)
...
(a-peano-eating-function-template (Successor-n a-num))
...]
は、...
ペアノ数で解決しようとしている特定の問題に固有のものになります。作業中のデータの構造に従う関数の問題です。Peano を食べる関数の具体例として、ピアノを星の束に変える関数を次に示します。
;; peano->stars: peano-number -> string
;; Turn a peano in a string of stars. We are all made of stars.
(define (peano->stars a-num)
(cond [(Zero? a-num)
""]
[(Successor? a-num)
(string-append "*"
(peano->stars (Successor-n a-num)))]))
とにかく、データ型は自然に特定の形状の関数につながります。これにより、位置表記法に戻ります。位置表記をデータ型としてキャプチャできますか?
できることがわかりました!10 進表記などの位置表記は、ペアノ数の記述と同様の方法で記述できます。この表現をBase10と呼びましょう。ここでは次のようになります。
Base10 :== Zero
| NonZero(q, r) where q is a Base10, and r is a digit.
Digit :== ZeroD | OneD | TwoD | ... | NineD
そして、構造を持つ言語でのプログラミングに関して具体的に理解したい場合は、
(define-struct Zero ())
(define-struct NonZero(q r))
(define-struct ZeroD ())
(define-struct OneD ())
(define-struct TwoD ())
(define-struct ThreeD ())
(define-struct FourD ())
;; ...
たとえば、数値42は Base10 で次のように表すことができます。
(NonZero (NonZero (Zero) (FourD)) (TwoD))
うわぁ。それは少し...非常識に見えます。しかし、これにもう少し頼りましょう。以前と同様に、Base10 を扱う関数は、多くの場合、Base10 の構造と一致する形状を持ちます。
;; a-number-eating-function-template: Base10 -> ???
(define (a-number-eating-function-template a-num)
(cond
[(Zero? a-num)
...]
[(NonZero? a-num)
... (a-number-eating-function-template (NonZero-q a-num))
... (NonZero-r a-num)]))
つまり、Base10 自体の構造に従うだけで、Base10 で動作する再帰関数の形をほぼ無料で取得できます。
... しかし、これは数字を扱うクレイジーな方法ですよね? ええと... forty-two の風変わりな表現を思い出してください:
(NonZero (NonZero (Zero) (FourD)) (TwoD))
同じ数を表す別の方法を次に示します。
((0 * 10 + 4) * 10 + 2)
ほぼ同じ考えです。ここで、さらにいくつかの括弧を削除しましょう。次の表記法で42を表すことができます。
42
私たちのプログラミング言語は、この数字の表記法を扱う方法を知るためにハードコーディングされています。
ゼロをチェックするのに相当するものは何ですか? 私たちはそれを知っています。
(= n 0) ;; or (zero? n)
NonZero のチェックに相当するものは何ですか? 簡単!
(> n 0)
NonZero-q と NonZero-r に相当するものは何ですか?
(quotient n 10)
(remainder n 10)
次に、ほとんどプラグアンドプレイで、数値入力を位置的に処理する再帰関数の形状を取得できます。
(define (a-decimal-eating-function-template n)
(cond [(= n 0)
...]
[(> n 0)
... (a-decimal-eating-function-template (quotient n 10))
... (remainder n 10)]))
見覚えがあります?:)
詳細については、How to Design Programsなどの教科書を参照してください。