素数夜曲とプログラミング B.2-B.4 Scheme, Python, Haskell, JavaScript 第 2 回

この記事は8分で読めます

このサイトは学部では早稲田で物理を, 修士では東大で数学を専攻し, 今も非アカデミックの立場で数学や物理と向き合っている一市民の奮闘の記録です. 運営者情報および運営理念についてはこちらをご覧ください.

理系のための総合語学・リベラルアーツの視点から数学・物理・プログラミング・語学 (特に英語) の情報を発信しています. コンテンツアーカイブに見やすくまとめているのでぜひご覧ください.


目次

素数夜曲とこれまでの内容

素数夜曲

これまでの内容

B.2 名前と手続き

リストの一番左は特別: そこには手続きを置く.
命名と抽象化について割といいことが書いてある気がする.

太字で強調してあるところがあって,
何となく大事そうだからこちらにも書かせてもらおう.

プログラミングは命令的知識を扱い, 数学は宣言的知識を扱う.

命令的というのは「如何にして為すか」という意味で,
宣言的というのは「何であるか」という意味.

B.2.1 関数の定義

素数夜曲では「函数」と書いているがめんどいので関数と書くことにする.
いわゆるラムダの話から入る.
次の関数は $f(x) = x^2$ だ.
ラムダで書くと $\lambda x.x^2$.

ラムダ以外の書き方もあるがとりあえず本の順に沿って書いていこう.


(lambda (x) (* x x))
(write ((lambda (x) (* x x)) 5))

RESULT

25

一次関数 $x \mapsto ax + b$ は Scheme だと次のように書く.


(lambda (a) (lambda (b) (lambda (x) (+ (* a x) b))))
(write ((((lambda (a) (lambda (b) (lambda (x)
                                    (+ (* a x) b))))
          2) 3) 5))

RESULT

13

こんな鬱陶しいのは嫌だ.
LISP が嫌いになる理由として括弧が挙げられる理由を強く感じる.
慣れるとむしろ便利なくらいと聞くが道は遠い.

何はともあれもう少し簡単な書き方がラムダレベルでもあるし Scheme にもある.
$\lambda abx.ax+b$ で, Scheme は次の通り.


((lambda (a b x)
   (+ (* a x) b))
 2 3 5)

(write ((lambda (a b x)
          (+ (* a x) b))
        2 3 5))

RESULT

13

素数夜曲でいつ出てくるのかわからないが,
大分長いこと lambda をひっぱるようだ.
鬱陶しいので適当に検索してきて lambda 抜きの関数定義を書いておく.


(define (hello name)
  (string-append "Hello " name "!"))
(write (hello "World"))

RESULT

"Hello World!"

Python の lambda

$f(x) = x^2$ を書いてみよう.


myfunc = lambda x: x ** 2
print(myfunc(2))

RESULT

4

普通の関数で書いてみよう.


def f(x):
    return x**2
print(f(2))

RESULT

4

Haskell の lambda

Haskell での lambda は次の通り.


mysquareInt = \x -> x ^ 2
mysquareDouble = \x -> x ** 2

main = do
  print $ mysquareInt 2
  print $ mysquareDouble 2.0

RESULT

4
4.0

JavaScript の lambda

一般的な lambda と無名関数の違いがよくわかっていない.


var f = function(a) { return Math.pow(a, 2) };
console.log(f(3));
var a = (function(a){ return Math.pow(a, 2) })(3);
console.log(a);
console.log((function(a){ return Math.pow(a, 2) })(3));

RESULT

9
9
9

B.2.2 アルファ変換

いわゆる束縛変数の文字を変えるというやつ.

B.3 ラムダ算法

Lambda calculus だし,
たぶんいわゆるラムダ計算なのだろう.
プログラムじたいはないのでばっさり省略.

B.3.3 イータ変換

省略.

B.3.4 ラムダ項の定義

略.

B.3.5 コンビネータ

略.

B.3.6 簡約の戦略

略.

B.4 特殊形式 (special form)

対象に名前をつけてプログラム上のどこからでも使えるようにすることを,
トップレベル定義 (top-level definition) という.

1 次関数を定義する.


(define linear
  (lambda (a b x)
    (+ (* a x) b)))
(write linear)
(newline)
(write (linear 2 3 5))

RESULT

#<procedure linear (a b x)>
13

最後の (linear 2 3 5) は $2 \times 5 + 3$ を計算している.

Lambda を使わない省略記法もあって MIT 記法 という.


(define (linear a b x)
  (+ (* a x) b))
(write linear)
(newline)
(write (linear 2 3 5))

RESULT

#<procedure linear (a b x)>
13

この手の省略記法は糖衣構文 (syntax sugar) と呼ばれる.

トップレベルは対話的入力時のプロンプトに象徴される領域で,
大域環境 (global environment) と呼び,
この環境下で定義された変数を大域変数 (global variable) と呼ぶ.
これと対になるのが局所環境 (local environment),
局所変数 (local variable) だ.

アルファ変換した linear# も書いておこう.


(define (linear# u v w)
  (+ (* u w) v))
(write linear#)
(newline)
(write (linear# 2 3 5))

RESULT

#<procedure #{linear#}# (u v w)>
13

アルファ変換は局所変数を別の文字 (列) に置き換えてもいいとか,
とりあえずそういう雑な理解をしている.
計算機科学的にもっと面倒なところまでカバーしている可能性があり,
そこまで調べていないからいまの理解の雑さは割と真剣に警戒している.

B.4.1 define

用語の定義があるから適当に書いておこう.
対象を挿入する場所 (スロット) は本文と同じくを角括弧 (<>) で書きたいが,
いろいろな事情から隅括弧 ([]) で書くことにする1.

Lambda 抜きの糖衣構文を使った define の一般形は次の通り.


定義方法: (define ([name] [fps]) [body])
利用方法: ([name] [aps])

本の記述が死ぬほどわかりづらいが,
とりあえず引用しよう.

手続の作用の対象となるのが引数 (parameter, argument) だ.
[fps] は関数内部で使われる仮引数 (formal parameters) の略記で,
[aps] は実際の処理対象である実引数 (actual parameters) の略記.
[body] は任意個数の (expression) で構成される手続の本体だ.
Scheme での式はアトムやリテラルを含めて評価値が戻ってくるもの全てを指す.

とりあえずここまでくり返し書いてきた関数定義を.


(define (linear a b x)
  (+ (* a x) b))

linear が [name], [fps] は a, b, x で,
[body] が (+ (* a x) b) だ.
[name] は要は関数の名前,
[fps] は定義した関数の引数,
[body] は関数で実際にやる処理のこと.

よくプログラミングのマニュアルでは上のタイプのよくわからない説明書きがある.
こういうやつ.


array explode ( string $delimiter , string $string [, int $limit = PHP_INT_MAX ] )

こういうのを見たらとりあえず実際にコード片を書いて実行して確認してみた方がいい.
めちゃくちゃ引数がたくさんあってそんなことやっていられないこともよくあるけれども.

Scheme の一般評価規則にしたがわない対象,
つまり特定の対象の評価値を求めないものを特殊形式 (special form),
あるは構文 (syntax) と呼ぶ.

スペシャルフォームと片仮名書きされているのをよく見かける.
ここからは代表的な特殊形式を紹介していくようだ.

一応改めて Python や Haskell での関数定義を書いておこう.

Python での変数・関数定義

まずは変数定義.


a = 1
print(a)

RESULT

1

数を 2 乗する関数を定義する.


def f(x):
    return x ** 2

print(f(2))
print(f(3))

RESULT

4
9

Haskell での変数・関数定義

私が理解している限り, いわゆる変数はない.
定数関数があってそれが定数の役割をしてくれるという理解.


two :: Int
two = 2

main = do
  print two

RESULT

2

こちらは整数を 2 乗する関数を定義する.


mySquare :: Int -> Int
mySquare x = x ^ 2

main = do
  print $ mySquare 2
  print $ mySquare 3

RESULT

4
9

JavaScript の変数・関数定義

use strict がないバージョン.
グローバル変数になるのでよくないと言われるやつ.


a = 2;
console.log(a);

RESULT

2

use strict はどこまで使えるようになっているのだろうか.
use strict をつけると上のキーワードなしの宣言でエラーになる.
次のキーワードはスコープとかの問題で詳しくはこことかで適当に調べる.


"use strict";
var a0 = 1;
console.log(a0);
var a = 2;
console.log(a);
let b = 3;
console.log(b);
const c = 4;
console.log(c);

RESULT

1
2
3
4

略号まとめ

  • proc: procedure
  • exp: expression
  • num: number
  • predi: predicate
  • func: function
  • var: variable
  • int: integer
  • consq: consequent
  • args: argument
  • val: value
  • str: string
  • altna: alternative
  • char: character
  • obj: object
  • lst: list
  • stm: stream

引数の有効範囲

変数定義的な意味での define の使い方.
正確には次のように言うべきのようだ.

記憶領域のある場所に a という名前を与え, その場所と値 3.14 を結びつける.

値を参照するラベルが a とかいうやつだろう.


(define a 3.14)
(write a)

RESULT

3.14

define の次の用法の例でもある模様.


定義方法: (define [name] [exp])

次のように読めばいいようだ.

[name] とは [exp] の名前である.

束縛と環境

a を定義したあとなら a を入力してもエラーが出ない.
REPL 的なアレで挙動確認しているわけではないので,
次のコード片では write を使っている.


(define a 3.14)
(write a)

RESULT

3.14

このように変数に束縛された値を確認する方法を変数参照 (variable reference) という.
次の実行結果を見ると a と定数 3.14 が関連づけられていることがわかる.


(define a 3.14)
(write a)
(newline)
(write (* 2 a))

RESULT

3.14
6.28

この a に値を格納するのを束縛 (bind) と呼ぶ.
この束縛情報の全体を環境 (environment) と呼ぶ.

B.4.2 lambda


定義方法: (lambda ([fps]) [body])
利用方法: ((lambda ([fps]) [body]) [aps])

改めてコード例を書いておこう.
数を 2 乗する関数を定義する.


(lambda (x) (* x x))
(write ((lambda (x) (* x x)) 2))

RESULT

4

関数定義の実験として前置記法 (prefix notation) を中置記法後置記法で書いている.
前置記法について prefix を作っている.


(define prefix
  (lambda (proc a b)
    (proc a b)))
(write (prefix + 2 3))
(newline)
(write (prefix * 2 3))

RESULT

5
6

これを中置記法 (infix notation), 後置記法 (postfix notation) で書く.


(define infix
  (lambda (a proc b)
    (proc a b)))
(write (infix 2 + 3))
(newline)

RESULT

5

(define postfix
  (lambda (a b proc)
    (proc a b)))
(write (postfix 2 3 +))

RESULT

5

Python, Haskell, JavaScript のコード例はいらないだろう.

ブロック構造

手続の評価値は内部の最後の式で決まる.


(define (linear x)
  (define a 2)
  (define b 3)
  (+ (* a x) b))
(write (linear 5))
(newline)

RESULT

13

次の式では最後の (* a b) の結果が返る.


(define (linear1 x)
  (define a 2)
  (define b 3)
  (+ (* a x) b)
  (* a b))
(write (linear1 3))

RESULT

6

関数内部で定義した変数は外で参照できない.
a は関数の中で定義されているだけでトップレベルで定義されていないから,
関数の外で呼ぼうとするとエラーになる.

次のコードを org-babel で実行するとエラーになる.
よくわからないが guile -s で実行した結果を貼っておこう.


(define a 3.14)
(write a)
(newline)

(define (linear2 x)
  (define a 2)
  (define b 3)
  (+ (* a x) b)
  (* a b))
(write (linear2 3))
(newline)

(write a)

RESULT

An error occurred.

guile -s での実行結果

3.14
6
3.14
その他の言語

同じようなスコープの問題がある.
JavaScript だとグローバル変数に関する有名な問題がある.
昔は必ず var を使えというのがあった.
最近は var よりも use strictlet, const を使うのがいいのだろうか.
識者のご意見求む.

begin

lambda の持つ列挙機能を含む特殊形式が begin.
式を対象にする限りは同じらしい.
begin だとトップレベル定義になる.


(begin
  (define a 2)
  (define b 3))
(write a)
(newline)
(write b)

上のコード, org-babel で実行できない.
guile -s での実行結果を書いておこう.

guile -s での実行結果

2
3
その他の言語

Python, Haskell, JavaScript で似たようなのがあるだろうか?

今回はこの辺で終わろう.


  1. 実体参照にすればいいのだが,
    今の私の腕ではスロットだけを自動で変換するスクリプトを作れないので諦めた.
    Markdown の引用の「>」や TeX 中の不等式を無視して,
    スロットだけを狙い打って実体参照に書き換えるコードを書く腕がない. 
  • このエントリーをはてなブックマークに追加
  • LINEで送る

関連記事

  • コメント (0)

  • トラックバックは利用できません。

  1. この記事へのコメントはありません。

このサイトについて

数学・物理の情報を中心にアカデミックな話題を発信しています。詳しいプロフィールはこちらから。通信講座を中心に数学や物理を独学しやすい環境づくりを目指して日々活動しています。
  • このエントリーをはてなブックマークに追加
  • LINEで送る

YouTube チャンネル登録

講義など動画を使った形式の方が良いコンテンツは動画にしています。ぜひチャンネル登録を!

メルマガ登録

メルマガ登録ページからご登録ください。 数学・物理の専門的な情報と大学受験向けのメルマガの 2 種類があります。

役に立つ・面白い記事があればクリックを!

記事の編集ページから「おすすめ記事」を複数選択してください。