このサイトは学部では早稲田で物理を, 修士では東大で数学を専攻し, 今も非アカデミックの立場で数学や物理と向き合っている一市民の奮闘の記録です. 運営者情報および運営理念についてはこちらをご覧ください.
理系のための総合語学・リベラルアーツの視点から数学・物理・プログラミング・語学 (特に英語) の情報を発信しています. コンテンツアーカイブに見やすくまとめているのでぜひご覧ください.
目次
はじめに: なぜ素数夜曲か
数学・物理学習とプログラミングと絡められないかと試行錯誤している.
数学からの取っつきやすさ,
物理からの取っつきやすさ,
プログラミングからの取っつきやすさをそれぞれ考えないといけない.
ある程度の体系性もほしい.
数学や物理としても面白い内容にしたいし,
プログラミングから見ても意味がある内容にしたい.
どうしたものかとずっと思っていたのだが,
素数という数学のキラーコンテンツとプログラミングを絡めた素数夜曲があった.
微妙なところも多いとは思いつつ,
メインエディタとして Emacs を使っているし,
LISP 系の言語はきちんとやりたいとも思っている.
そんなところでいい感じの落とし所という気がしたので,
少しずつ読み進めつつコードの記録をしていこうと決めた.
数学・物理の数値計算との相性の良さ,
私の勉強してみたさとも合わせて Python や Haskell のコードも書いていけたらいいな,
と思っている.
あと何となく JavaScript もやってみよう.
JavaScript は動きが速すぎて何かやってもすぐに動かなくなりそうで嫌なのだが,
ここでやるくらいのことならそう簡単に陳腐化しないだろうと勝手に信じてやっていこう.
基本的には付録のプログラミングパート,
特に付録 B から記録をつけていく予定だ.
Emacs の org-babel で書いているので,
素数夜曲本編とは見た目ちょっと違うコードを書いていくかもしれない.
Scheme, org-babel ともにあまりよくわかっていないがとりあえず進める.
org-babel の Scheme が標準で Guile を使っていて,
その変更の仕方がわからなかったのでとりあえず Guile を使う.
これまで入れていた Gauche を使いたかったが org-babel が対応していないようだ.
そうでないなら MIT/GNU Scheme が良かった気もするがこれも設定がわからない.
でははじめよう.
出力用関数
(write "write test")
(newline)
(display "display test")
RESULT
"write test"
display test
B.1 評価値を得る
とりあえず Hello, World! で.
(write "Hello, Scheme!")
RESULT
"Hello, Scheme!"
B.1.2 四則計算
基本的な書き方
加減乗除は次の通り.
前置記法なのがポイント.
割り算で分数にしてくれるのは割とポイント高い気がする.
(write (+ 5 3))
(newline)
(write (- 5 3))
(newline)
(write (* 5 3))
(newline)
(write (/ 5 3))
RESULT
8
2
15
5/3
Python
print(5 + 3)
print(-5 + 3)
print(5 * 3)
print(5 / 3)
RESULT
8
-2
15
1.6666666666666667
Haskell
まだ (?) org-babel との相性がよくないっぽい.
ここ を参考にちょっと改造.
Haskell でのあまりの計算には mod を使う.
main = do
print $ 5 + 3
print $ -5 + 3
print $ 5 + 3
print $ 5 / 3
print $ mod 5 3
print $ 5 `mod` 3
RESULT
8
-2
8
1.6666666666666667
2
2
JavaScript
console.log(5 + 3);
console.log((-5 + 3));
console.log(5 * 3);
console.log(5 / 3);
RESULT
8
-2
15
1.6666666666666667
長めの計算をどう書くか
例えば次の式.
\begin{align}
2 \div 3 + 5 \times 7 – 11
\end{align}
Scheme (LISP 系言語) だと括弧で細かく区切っていくから,
演算の優先度みたいな面倒なことをあまり考えなくても済む.
(define a (- (+ (/ 2 3) (* 5 7)) 11))
(write a)
RESULT
74/3
Python
Python だと普通に書く.
a = 2 / 3 + 5 * 7 - 11
print(a)
RESULT
24.666666666666664
Haskell
Python っぽくも Scheme っぽくも書ける.
演算子を括弧でくくると Scheme のように演算子を前に置けるから.
main = do
print $ 2 / 3 + 5 * 7 - 11
print $ (-) ((+) ((/) 2 3) ((*) 5 7)) 11
RESULT
24.666666666666664
24.666666666666664
JavaScript
console.log(2 / 3 + 5 * 7 - 11);
RESULT
24.666666666666664
平方根を取る関数 (演算子?)
素数夜曲 P.389 によると Scheme で sqrt は単項演算子らしい.
(write (sqrt 2))
RESULT
1.4142135623730951
入れ子にすれば複数回適用できる.
Scheme は関数合成あるのだろうか.
ここを見ると Gauche ならあるようだが.
(write (sqrt (sqrt 4)))
RESULT
1.4142135623730951
Python
Python 版は次の通り.
Python は math をインポートしないといけないのがめんどい.
Python 3.5.1 を使っているが関数合成はないのだろうか.
ちょっと調べたところでは標準ではなさそうだった.
import math
print(math.sqrt(2))
print(math.sqrt(math.sqrt(4)))
RESULT
1.4142135623730951
1.4142135623730951
Haskell
Haskell は関数合成 (.)
がある.
main = do
print $ sqrt 2
print $ sqrt $ sqrt 4
print $ (sqrt . sqrt) 4
RESULT
1.4142135623730951
1.4142135623730951
1.4142135623730951
JavaScript
console.log(Math.sqrt(2));
console.log(Math.sqrt(Math.sqrt(4)));
RESULT
1.4142135623730951
1.4142135623730951
自然対数の底, ネイピア数
Scheme では exp を使えば出せる.
$e$ 自体は定義されていない?
(write (exp 1))
RESULT
2.718281828459045
Python
Python の場合はやはり math モジュールにある.
import math
print(math.e)
RESULT
2.718281828459045
NumPy にもある.
import numpy as np
print(np.e)
RESULT
2.718281828459045
Haskell 版
$e$ 自体は定義されていないので Scheme と同じく指数関数から近似値を出す.
main = do
print $ exp 1
RESULT
2.718281828459045
JavaScript 版
Math.exp()
が自然対数の底による指数関数で,
Math.pow()
は一般の底と指数の関数.
console.log(Math.exp(1));
console.log(Math.pow(2, 3));
RESULT
2.718281828459045
8
2 項演算子をいくつか
まずは等号 = を.
(write (= 1 1))
(newline)
(write (= 1 2))
RESULT
#t
#f
ここで #t
は true, #f
は false を意味している.
==
ではないのかとちょっと驚いた.
ちなみに引数がたくさんある場合にも適用できる.
(write (= 1 2 3 4 5 6))
(newline)
(write (= 1 1 1))
(newline)
(write (= 1 1 1 2))
RESULT
#f
#t
#f
不等号 >
>=
などもある.
不等号はそれぞれ隣の項に対して適用させていった結果を出力するようだ.
(write (< 1 2 2 3))
(newline)
(write (< 1 2 3 4))
RESULT
#f
#t
Python
等しくないことの判定は !=
だ.
print(1 == 2)
print(1 == 1)
print(1 != 1)
print(1 < 2)
RESULT
False
True
False
True
こちらは =
だと代入になってしまうので ==
と重ねる.
Haskell
Scheme の (= 1 2 3 4) みたいなのはどう書けばいいのだろう.
foldr で書けると思ったので foldr (==) 1 [1, 2, 3, 4]
と書いてみたら怒られた.
相談したら「それ型エラー起こしてるから. ちゃんとして」と言われた.
Haskell, ちょっとしたところですぐこけるのでとてもつらい.
結論からいうと適当に 2 つリストを作り,
その要素を比較していく形で対処するのが普通らしい.
他のところでもそういう処理をするのがいいというコメントを頂いたことがあるので,
道具箱におさめておこう.
main = do
print $ 1 == 1
print $ 1 /= 1
print $ 1 < 2
let xs = [1, 2, 3, 4]
print $ and $ zipWith (==) xs (tail xs)
let ys = [1, 2, 3, 4]
print $ and $ zipWith (<) ys (tail ys)
-- print $ foldr (==) 1 [1, 2, 3, 4] -- エラーになった
RESULT
True
False
True
False
True
let を使うのが癪なので次回やる予定の lambda を使って書いてみる.
main = do
print $ (\xs -> and $ zipWith (==) xs (tail xs)) [1, 2, 3, 4]
print $ (\xs -> and $ zipWith (<) xs (tail xs)) [1, 2, 3, 4]
RESULT
False
True
見づらい気もするので無理せず関数定義した方がいい気もする.
mycompare :: [Int] -> Bool
mycompare xs = and $ zipWith (<) xs (tail xs)
main = do
print $ mycompare [1, 2, 3, 4]
RESULT
True
Haskell 追記
またコメントを頂いてしまった.
これ.
and $ ap (zipWith (<)) tail [1..4]
True
and $ ap (zipWith (<)) tail [1,2,2,3]
False
実行してみよう.
しばらくはまったが何となく ap
を使うためにインポートが必要っぽい.
import Control.Monad
main = do
print $ and $ ap (zipWith (<)) tail [1..4]
print $ and $ ap (zipWith (<)) tail [1,2,2,3]
RESULT
True
False
最高か.
そして「素人はこんなこともわからない」というのを思い出せさてくれるし,
とてもいい体験だった.
ちょっと調べはしたが ap
が何なのかあまりよくわかっていない.
とりあえずこれ.
myap f g = \x -> f x (g x)
main = do
print $ and $ myap (zipWith (<)) tail [1..4]
print $ and $ myap (zipWith (<)) tail [1,2,2,3]
RESULT
True
False
stackoverflow もきちんと全部読んだわけではないし,
この雑な理解でいいとも思わないがとりあえずこれで掴まえられるところもある.
「適当にしか理解していない (そして半端な理解だと割とどうでもいいところで無駄にはまる)」ことさえ理解しておけば,
あとでいくらでも調節は効くと数学でいつも経験することをいつも通り信じて追記を終える.
JavaScript
比較には ===
と ==
がある.
基本的に ===
でやるのが安全.
console.log(1 === 2);
console.log(1 === 1);
console.log(1 !== 1);
console.log(1 < 2);
RESULT
false
true
false
true
JavaScript で複数の比較はどう書くといいだろうか.
reduce を使おうと思ったが,
それは Haskell の最初の foldr でエラーになるのと同じ理由で駄目だ.
ここを見ると zip がないようだ.
ふつうにループを回すのでは面白くない.
大文字小文字の区別
~~素数夜曲 P.390 の記述によると,~~
~~Scheme は原則として大文字小文字を区別しないらしいが実装により異なるとのこと.~~
いわゆる処理系で変わるとかいうやつか?
安全性を考えるなら小文字に統一するのがいいらしい.
区別しないことを前提にキャメルケースを使うのも一手とか何とか.
今回はこのくらいにしよう.
Scheme 追記
下にあるようにコメントを頂いた.
Scheme の仕様は改定を重ねられているので、どの版であるかによって挙動が異なる部分があります。 Scheme の仕様はその正式名称を略して RnRS と呼ばれていて、 n の部分に何回目の改定であるかの番号が入ります。 最新は R7RS です。
「大文字小文字を区別しない」というのは R5RS までの仕様です。 (ただ、 R5RS 処理系を名乗る処理系でも区別する処理系や、切り替えられる処理系はあります。) R6RS 以降では区別するのがデフォルトです。
有理数や無限長整数などは R6RS 以外ではオプショナルな仕様です。 (R6RS で必須になった後、 R7RS では再びオプショナルになりました。) 処理系によっては桁あふれに配慮が必要な場合もあります。 有理数や無限長整数は Guile や Gauche では問題なく使えますので気にする必要はありませんが、その他に色々なところで仕様では未規定の部分があるので処理系を乗り換える機会にはつまらないことで躓くかもしれません。
思っていたよりも面倒そうな話だった.
ただ「ハマる可能性があるかもしれない」
「ハマるところはこの辺」というのが頭の片隅にあるかないかだけでも
全く違うことはこれまでの経験でよくわかっている.
こんなありがたいことはない.
こういうコメントを頂けるのが記事にしておく醍醐味だ.
地道に続けよう.
Scheme の仕様は改定を重ねられているので、どの版であるかによって挙動が異なる部分があります。 Scheme の仕様はその正式名称を略して RnRS と呼ばれていて、 n の部分に何回目の改定であるかの番号が入ります。 最新は R7RS です。
「大文字小文字を区別しない」というのは R5RS までの仕様です。 (ただ、 R5RS 処理系を名乗る処理系でも区別する処理系や、切り替えられる処理系はあります。) R6RS 以降では区別するのがデフォルトです。
有理数や無限長整数などは R6RS 以外ではオプショナルな仕様です。 (R6RS で必須になった後、 R7RS では再びオプショナルになりました。) 処理系によっては桁あふれに配慮が必要な場合もあります。 有理数や無限長整数は Guile や Gauche では問題なく使えますので気にする必要はありませんが、その他に色々なところで仕様では未規定の部分があるので処理系を乗り換える機会にはつまらないことで躓くかもしれません。
ありがとうございます。
記事にも追記しておきました。
大文字小文字はともかく有理数と無限調整数の話はちょっと驚きました。
Haskell の let がお嫌いならば ap は如何でしょうか
ありがとうございます。
記事中にも追記しました。
ありがてえありがてえ