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

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

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

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


目次

はじめに: なぜ素数夜曲か

数学・物理学習とプログラミングと絡められないかと試行錯誤している.

数学からの取っつきやすさ,
物理からの取っつきやすさ,
プログラミングからの取っつきやすさをそれぞれ考えないといけない.
ある程度の体系性もほしい.

数学や物理としても面白い内容にしたいし,
プログラミングから見ても意味がある内容にしたい.

どうしたものかとずっと思っていたのだが,
素数という数学のキラーコンテンツとプログラミングを絡めた素数夜曲があった.

微妙なところも多いとは思いつつ,
メインエディタとして 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 では問題なく使えますので気にする必要はありませんが、その他に色々なところで仕様では未規定の部分があるので処理系を乗り換える機会にはつまらないことで躓くかもしれません。

思っていたよりも面倒そうな話だった.
ただ「ハマる可能性があるかもしれない」
「ハマるところはこの辺」というのが頭の片隅にあるかないかだけでも
全く違うことはこれまでの経験でよくわかっている.
こんなありがたいことはない.
こういうコメントを頂けるのが記事にしておく醍醐味だ.

地道に続けよう.

  • このエントリーをはてなブックマークに追加
  • LINEで送る

関連記事

  1. 2015 08.06

    ニコニコ動画

  • コメント (4)

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

  1. Scheme の仕様は改定を重ねられているので、どの版であるかによって挙動が異なる部分があります。 Scheme の仕様はその正式名称を略して RnRS と呼ばれていて、 n の部分に何回目の改定であるかの番号が入ります。 最新は R7RS です。
    「大文字小文字を区別しない」というのは R5RS までの仕様です。 (ただ、 R5RS 処理系を名乗る処理系でも区別する処理系や、切り替えられる処理系はあります。) R6RS 以降では区別するのがデフォルトです。
    有理数や無限長整数などは R6RS 以外ではオプショナルな仕様です。 (R6RS で必須になった後、 R7RS では再びオプショナルになりました。) 処理系によっては桁あふれに配慮が必要な場合もあります。 有理数や無限長整数は Guile や Gauche では問題なく使えますので気にする必要はありませんが、その他に色々なところで仕様では未規定の部分があるので処理系を乗り換える機会にはつまらないことで躓くかもしれません。

      • phasetr
      • 2016年 12月29日

      ありがとうございます。
      記事にも追記しておきました。

      大文字小文字はともかく有理数と無限調整数の話はちょっと驚きました。

  2. Haskell の let がお嫌いならば ap は如何でしょうか

    and $ ap (zipWith (<)) tail [1..4]
    True

    and $ ap (zipWith (<)) tail [1,2,2,3]
    False

      • phasetr
      • 2016年 12月31日

      ありがとうございます。
      記事中にも追記しました。

      ありがてえありがてえ

このサイトについて

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

YouTube チャンネル登録

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

メルマガ登録

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

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

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