「ほっ」と。キャンペーン

カテゴリ:Haskell( 699 )

timer_create が WSL にインプリメントされていない。

Bash on Ubuntu on Windows に ghc をインストールしようとしたが失敗した。timer_create というシステムコールが WSL にインプリメントされていないからだそうだ。

sudo apt-get install hugs

で hugs は無事インストールできた。Windows 用の Haskell Platform はインストール済みなので、hugs はいらないかもしれないのだが Haskell 中毒になっていてどの OS でも Haskell が使えないと落ち着かない。

WSL もこれから Windows とシームレスにつながっていくのだろうか。そうなると Windows から数多くの Linux のアプリケーションが利用できるので便利だ。これらは商用に開発されてはいないが、ニッチな領域でこれしかないというソフトも多い。WSL の流れがこれからも進んでいくと面白い。

[PR]
by tnomura9 | 2016-11-02 13:04 | Haskell | Comments(0)

消費者ローンの利息計算

消費者金融豆知識というサイトに利息の計算方法という記事があった。

30万円を実質年率18%で借りて、毎月1万円の返済をしたとすると、1か月にかかる金利は

利息 = 利用残額 * 実質年率 / 365 * 利用日数になる。

これを Haskell で計算すると次のようになる。

Prelude> :{
Prelude| risoku :: Int -> Int
Prelude| risoku g = round $ fromIntegral g * 0.18 / 365 * 30
Prelude| :}
Prelude> risoku 300000
4438

したがって、最初の月に1万円支払うと利息が4438円になるので、元金の返済に使われる金額は 10000 - 4438 = 5562 円になる。その分が元金から差し引かれるので1か月後の借り入れは 300000 - 5562 = 294438 円 になる。つまり1か月後の1万円の返済で借り入れた元金が 294438 円になることになる。これを gankin という関数にしてみた。

Prelude> :{
Prelude| gankin :: Int -> Int
Prelude| gankin g = g - (10000 - risoku g)
Prelude| :}
Prelude> gankin 300000
294438

そこで1か月ごとに元金が減っていく過程をリストにしてみた。停止条件を考えるのが面倒なので無限リストを吐き出すようにした。

Prelude> :{
Prelude| gankins :: Int -> [Int]
Prelude| gankins g = g : gankins (gankin g)
Prelude| :}
Prelude> take 10 $ gankins 300000
[300000,294438,288794,283067,277255,271357,265372,259298,253134,246879]

10回くらいでは25万円も借り入れが残ることになる。どれくらい返済すればいいのかは元金の値が正の場合を取り出せばいい。

Prelude> takeWhile (>0) $ gankins 300000
[300000,294438,288794,283067,277255,271357,265372,259298,253134,246879,240531,234090,227553,220920,214188,207357,200425,193390,186251,179006,171654,164194,156623,148940,141143,133231,125202,117054,108786,100395,91880,83239,74470,65572,56542,47379,38080,28643,19067,9349]

何回くらい返済するのかは length 関数を使えば分かる。

Prelude> length $ takeWhile (>0) $ gankins 300000
40

なんと40回(3 年 4 か月)の支払いで30万円の借り入れに対し40万円支払うことになる。

このように Haskell でプログラムするときは段階を追って、部分的な関数を確認しながらプログラムできるので、一般的な説明をすぐにプログラムに落とし込めることが多い。Haskell は日常生活にプログラミングの利便性を持ち込んでくれる。

この記事を literate program にしてみた。

>module Main where

>risoku :: Int -> Double -> Int
>risoku g r = round $ fromIntegral g * r / 365.0 * 30.0

>gankin :: Int -> Double -> Int -> Int
>gankin g r pay = g - (pay - risoku g r)

>gankins :: Int -> Double -> Int -> [Int]
>gankins g r pay = g : gankins (gankin g r pay) r pay

>loan :: Int -> Double -> Int -> [Int]
>loan g r pay = takeWhile (>0) $ gankins g r pay

この記事全体を loan.lhs というファイルにして ghci から :l コマンドでロードすると loan 関数を利用できる。実行例は次のようになる。

Prelude> :l loan.lhs
[1 of 1] Compiling Main ( loan.lhs, interpreted )
Ok, modules loaded: Main.
*Main> loan 300000 0.18 10000
[300000,294438,288794,283067,277255,271357,265372,259298,253134,246879,240531,234090,227553,220920,214188,207357,200425,193390,186251,179006,171654,164194,156623,148940,141143,133231,125202,117054,108786,100395,91880,83239,74470,65572,56542,47379,38080,28643,19067,9349]

[PR]
by tnomura9 | 2016-10-31 18:50 | Haskell | Comments(0)

ghci で複数行を入力する方法 :{ :} コマンド

ghci で関数の定義をするのに複数行入力したいときがある。:set +m コマンドで複数行入力モードにすることができるが、こんどは1ぎょうで定義が済むときにも複数行入力になって煩わしく感じる。こんな時は :{ コマンド と :} コマンドを使えばいいらしい。: が先頭についているのでどちらも ghci のコマンドだ。

使い方は簡単で複数行入力したいときに :{ コマンドだけを入力すると複数行入力モードになる。入力が終わったら、複数行入力モード内で :} コマンドだけを入力する。すると、再び1行入力モードになる。複数行で定義された関数は1行モードでちゃんと使える。

Prelude> :{
Prelude| fact 0 = 1
Prelude| fact n = n * fact (n-1)
Prelude| :}
Prelude> fact 5
120

複数入力モードの時は、エディターから直接にコピペできる。ファイルを作るまではないが、入力ミスしたら再度入力をし直さなければならないときなどに便利だ。

Prelude> :{
Prelude| mymap f [] = []
Prelude| mymap f (x:xs) = (f x) : mymap f xs
Prelude| :}
Prelude> mymap (*10) [1..10]
[10,20,30,40,50,60,70,80,90,100]

ghci を使って部品のテストをしてからプログラムを組むようにしたら、全体が完成しないとテストができないプログラムを作成するのが嫌になる。:{ と :} コマンドはこれからうんと使いそうだ。


[PR]
by tnomura9 | 2016-10-31 11:00 | Haskell | Comments(0)

お気楽 Haskell プログラミング入門

Haskell プログラムの書き方だけでなく、Haskell とそのデータ構造でなにができるのかを解説したサイトがないかと思って探していたら、あった。M. Hiroi さんの

お気楽 Haskell プログラミング入門

がそれだ。しばらく、このサイトで学習してみようと思った。「思不学則殆」だ。いろいろな学習方法があるだろうが、論語の学習法について述べたものを調べるだけでも随分参考になるだろう。こんどは、そんなサイトがないか、探してみよう。
[PR]
by tnomura9 | 2016-10-31 07:32 | Haskell | Comments(0)

randomRs

randomRs

何かのシミュレーションをしようと思う時、まとまった数のある範囲の数の乱数が欲しい時は多い。そんな時は、Haskell では System.Random モジュールの randomRs 関数を使う。randomRs 関数の書式は次のようになる。

randomRs <乱数の範囲を示すペア。 (1,6) などのこと> <乱数発生器> :: [ 数値のデータ型 ]

randomRs 関数はこのような乱数の無限配列を発生させるので必要な数だけ take n 関数で切り取らないといけない。

こういう抽象的な説明は、実例がないと分からない。抽象的な説明は、いろいろな可能性に当てはまるようにパターンを説明するが、パターンを目に見えるように表現するのは難しいからだ。多分、この記事のはじめの部分を読んでいる人も「乱数が欲しい」という部分以外は読み跳ばすのではないだろうか。そこでサイコロを10回振った時の出た目のシミュレーションをする実例を次に示す。

Prelude System.Random> take 10 $ randomRs (1,6) (mkStdGen 7)
[6,1,4,5,3,2,3,6,6,6]

今度は実例なのでわかりやすいと思いきや、やはり読み飛ばしてしまうだろう。

take 10 $ randomRs (1,6) (mkStdGen 7)

は単なる記号の配列だからだ。この記号の配列には意味が込められているが、その意味を読み取らない限り、単なる記号の配列だ。こんな時は、記号の配列の部分配列の意味を探ることになる。

まず、take 10 はリストの先頭部分の10個の要素を切り取ったリストを作るプログラムだ。これも抽象的な説明になってしまうので ghci で実験してみないと分からない。

Prelude System.Random> take 10 [1..]
[1,2,3,4,5,6,7,8,9,10]

これを見れば take 10 が無限リスト [1..] の先頭部分を切り取る関数であることがわかる。このように記号の配列の意味は、その記号配列の単語の意味が少なければ容易になる。

$ の意味は右項の値を左項の関数の引数にする演算子だが、詳しい説明は省略する。説明しようとすると抽象的になるし、ghci で作業をしている時は頻繁に使うからだ。この $ の意味は文字情報としてではなく操作的なイメージとして理解されているはずだ。

したがって、次の記号配列(文)の配列の意味が分かれば Haskell で乱数を発生させることができることが分かる。

randomRs (1,6) (mkStdGen 7)

ramdomRs の random は乱数という意味だ。R はおそらく範囲 range の意味だろう。最後の s はこの関数が複数の乱数を発生させることを示している。この場合発生するのは乱数のリストであることが分かる。

randomRs は関数なので引数を必要とする。上の文を見る限り (1,6) という整数のペアと (mkStdgen 7) という関数 mkStdGen の値が引数になる。

(1,6) は乱数の範囲だ。サイコロの目だから 1 - 6 までの整数だ。そこで、問題になるのは mkStdGen 関数とその引数の意味だ。mkStdGen はおそらく make standard generator という意味だろう。標準的な乱数発生器を作るという意味になる。mkStdGen の引数は 7 という整数なので、mkStdGen を整数 7 に関数適用すると乱数発生器が製造できることになる。そこで乱数発生器を作ってみた。

Prelude System.Random> mkStdGen 7
8 1

なにやら2つの整数が現れたが、mkStdGen 7 の実際のデータ型はどのようなものなのだろう。

Prelude System.Random> :t mkStdGen 7
mkStdGen 7 :: StdGen

やはり、整数のペアなどではなく StdGen 型の乱数発生器だ。個々の引数の意味がわかったので randomRs 関数を使って見ることにしたが、実際に ghci で実行しようとすると、

Prelude System.Random> randomRs (1,6) (mkStdGen 7)
[6,1,4,5,3,2,3,6,6,6,4,2,2,2,3,6,2,2,3,5,6,3,3,5,1,3,3,6,6,3,3,4,4,4,6,1,5,5,3,6 ...

とものすごい勢いで無限リストが吐き出されるので、慌てて Ctrl + C で表示を止める羽目になる。しかし、これで体験的に take 10 のありがたさが分かる。こういう過程を経て次のプログラムの意味が理解でき、乱数を使いたい時に空で入力でき、自分の用途にも応用できるようになる。

Prelude System.Random> take 10 $ randomRs (1,6) (mkStdGen 7)
[6,1,4,5,3,2,3,6,6,6]

しかし最後に問題が残る。最初に述べた文法規則の :: [数値の型] の意味である。これも次のように実験することによって意味を理解できる。

Prelude System.Random> take 10 $ randomRs (1,6) (mkStdGen 7) :: [Double]
[4.26675933701571,5.442686990051323,5.973406609345734,4.535124339577418,5.247913261418159,4.600667572473583,3.895456822974551,3.9329009497006684,2.9457141491053873,4.047582467765828]

つまり型指定によって整数や実数の乱数を作成できるのだ。ここまで知れば「ある範囲のまとまった数の乱数」を Haskell で自在に作成できるようになる。

長々と説明してきたが、これが人に知識を伝達するときの知識の展開である。

take 10 $ randomRs (1,6) (mkStdGen 7)

という簡潔な文を理解するためには、上のような長々とした説明が必要になり。その説明の中には ghci 上で操作して実験するという、非言語的な知識も含まれてくる。このように、知識の展開を行って脳の中の知識を相手に伝えようとするときは、言語的な情報とともに、非言語的な操作的知識も含まれていることを意識しておくことが大切だ。

しかし、この長々とした過程を通り抜けると、知識は上のような簡潔な式に圧縮され、「ある範囲の乱数のまとまったリストが欲しい」という漠然とした抽象的なアイディアを実体化できるようになる。

つまり、アイディアを形にする時には、上の式のような簡潔な圧縮された知識が必要だ。一方、その圧縮された知識を自分の脳内に作成したり他の人に伝達するためには、今までわざと長々と説明してきたような、多量の知識の展開が必要なのだ。

知識の活用のためにはその圧縮が不可欠だし、その圧縮された知識の習得のためにはこれを一旦大量に展開する必要があるということがこの例でも分かる。


[PR]
by tnomura9 | 2016-10-26 07:47 | Haskell | Comments(0)

順列プログラム

順列プログラム

Haskell で順列を作るプログラムはどのようにして作成できるのだろうか。まず、順列を手作業で作ることを考えてみる。

順列はある集合の要素を並べていく方法が何通りあるかということを調べるのが目的だ。しかし、こういう抽象的な言い方は解決に結びつかないことが多い。こういう時は、具体的な集合を例にとって、どういうメカニズムが働いているかを確かめてみる必要がある。

そこで具体的な集合 {1, 2, 3} について順列を作ってみることを試みてみる。順列とはこの集合の要素を順番に並べていくことだ。例えば {1,2,3} のうち 1 を取り出して先頭に置く。つまり、

1

である。集合 {1,2,3} から 1 を取り出すと残りは {2, 3} である。つまり 1 の右に置くのは 2, 3 のいずれかであるということになる。そこで 2 を 1 の右横に並べてみる。

1,2

集合 {1, 2, 3} から 1, 2 を取り出したので残りは { 3 } しか残っていない。そこでこれを取り出して 1,2 の右横に並べる。

1,2,3

これで集合 {1, 2, 3} の要素を並べる並べ方の一つが示された。しかし先頭が 1 でも二番目に 3 を置く置き方もある。この時は 1, 3 を並べた時に残る要素は 2 だけだから、結局、

1,3,2

という並べ方になる。つまり先頭の要素が 1 の場合にはそのあとに並べる要素の並べ方は、{2, 3} のどちらかを並べるので 2 通りになるし、二番目まで並べた後は残る要素は 1つだけだから、結局のところ 2 * 1 通りになる。すなわち 1,2,3 と 1,3,2 である。

次に、先頭の要素が 2 の場合を考える。これは上の議論と同じ推論で 2,1,3 と 2,3,1 の2通りになることがわかる。先頭が 1 の場合が2通り、先頭が 2 の時が 2 通りである。先頭に置くことのできる要素は 1, 2 の他に 3 がある。先頭が 3 の場合は 3, 1, 2 と 3, 2, 1 の 2通りだから結局のところ集合 {a, b, c} の要素を全て一列に並べる並べ方は、(1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), (3,2,1) の6通りになることがわかる。すなわち、3 * 2 * 1 通りである。

次に問題になるのは、このように手作業で行われた処理をどうやってプログラムに落とし込むかだ。そこで集合 {1, 2, 3} から先頭の要素を選ぶプログラムを考える。この場合集合 {1, 2, 3} を Haskel のリスト [1, 2, 3] で表すことにする。リスト [1, 2, 3] から要素1つだけ取り出すには Haskell の内包的定義を利用する。次のプログラムはリストの要素1個を全て取り出すプログラムだ。あとで Data.List モジュールの delete 関数を使うのでこれを import しておく。

Prelude> import Data.List
Prelude Data.List> let test1 xs = [x | x <- xs]
Prelude Data.List> test1 [1..3]
[1,2,3]

内包的定義 [x | x <- xs] は集合の内包的定義 { x | x ∈ A } と同じように リスト xs の要素を一つづつ取り出してそれをリストとして返す。上の例では要素をそのままリストにしただけだが、x の値は加工することができる。次のプログラムは map (*2) [1..3] と同じ動作をする。

Prelude Data.List> let test2 xs = [x*2 | x <- xs]
Prelude Data.List> test2 [1..3]
[2,4,6]

そこで取り出した先頭の要素をリスト [5,6] の先頭に追加するプログラムを作ってみる。

Prelude Data.List> let test3 xs = [x:[5,6] | x <- xs]
Prelude Data.List> test3 [1..3]
[[1,5,6],[2,5,6],[3,5,6]]

これは xs の要素を先頭に並べる操作と同じだ。それではリストのリスト [[5,6], [7,8]] の先頭に xs の要素を追加する場合はどうなるだろうか。

Prelude Data.List> let test4 xs = [map (x:) [[5,6], [7,8]] | x <- xs]
Prelude Data.List> test4 [1..3]
[[[1,5,6],[1,7,8]],[[2,5,6],[2,7,8]],[[3,5,6],[3,7,8]]]

test4 の結果はリストのリストになってしまうのでこれを concat を使ってリストにする。

Prelude Data.List> let test5 xs = concat [map(x:) [[5,6], [7,8]] | x <- xs]
Prelude Data.List> test5 [1..3]
[[1,5,6],[1,7,8],[2,5,6],[2,7,8],[3,5,6],[3,7,8]]

これはリスト xs の要素を順に取り出して、リストのリスト [[5,6], [7,8]] の要素 [5,6] と [7,8] の先頭に追加してできる要素のリスト(例えば [1,5,6] )を全て集めたリストを作り出す。これを利用すれば順列を計算する関数 perm xs の recursive case を作ることができる。

perm xs の結果がリスト xs = [1,2,3] から計算されたものであるとすると手作業で順列を作った場合の議論から、

[[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2],[3,2,1]]

のようなリストのリストになるはずだ。このうち先頭の2つの要素 [1,2,3] と [1,3,2] についてみると、これは 1:[2,3] と 1:[3,2] と同じものだ。つまりこれは xs の要素の一つである 1 をその要素を xs から取り除いたリスト [2,3] の順列 [[2,3], [3,2]] の要素の先頭部分に追加したものだ。これをプログラムで書くと map (1:) [[2,3], [3,2]] になる。

Prelude Data.List> map (1:) [[2,3],[3,2]]
[[1,2,3],[1,3,2]]

同じように xs の要素 2, 3, についても考えてみる。

Prelude Data.List> map (2:) [[1,3], [3,1]]
[[2,1,3],[2,3,1]]
Prelude Data.List> map (3:) [[1,2], [2,1]]
[[3,1,2],[3,2,1]]

これらをまとめると次のようなリストのリストのリストができる。

Prelude Data.List> [map (1:) [[2,3],[3,2]], map (2:) [[1,3],[3,1]], map (3:) [[1,2],[2,1]]]
[[[1,2,3],[1,3,2]],[[2,1,3],[2,3,1]],[[3,1,2],[3,2,1]]]

リストのリストのリストでは困るので concat を使ってこれをリストのリストに整形する。

Prelude Data.List> concat [map (1:) [[2,3],[3,2]], map (2:) [[1,3],[3,1]], map (3:) [[1,2],[2,1]]]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

これらの操作を抽象化すると次の perm xs の漸化式 (recursive case) を作ることができる。

perm xs = concat [map (x:) $ perm (delete x xs) | x <- xs]

recursive case だけでは無限再帰が発生してしまうので perm が空リストの時の base case も必要だ。

perm [] = [[]]

perm [] の値が [[]] になっているのは perm xs の値の型がリストのリストだからだ。

このプログラムは次のように ghci 上で確認できる。

Prelude Data.List> let perm [] = [[]]; perm xs = concat [map (x:) $ perm (delete x xs) | x <- xs]
Prelude Data.List> perm [1,2,3]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

また、バード・スタイルで書いた次の perm xs のプログラムは、この記事をコピペしてテキストファイルにしておくと、そのテキストファイルを ghci にロードして実行してみることができる。

> import Data.List
> perm [] = [[]]
> perm xs = concat [map (x:) $ perm (delete x xs) | x <- xs]

実行例は次のようになる。

Prelude> :l permutation.lhs
[1 of 1] Compiling Main ( permutation.lhs, interpreted )
Ok, modules loaded: Main.
*Main> perm [1..3]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

今となっては perm のプログラムは空でかけるが、最初の頃は concat がなぜそこになければならないのかが全く分からなかった。それは、成書には今まで長々と書いてきた説明に全く触れられていなかったからだ。参考書にはプログラムがポンと置いてあるだけで、それについての説明は自分で考えなければならなかった。これらの説明をする紙面が到底とれないからだ。これを見ても literate programming の教育的な意味がわかる。

Haskell のプログラムは簡潔に高機能なプログラムを書くことができるが、それを理解するためには、膨大な暗黙知が必要とされる。これが、Haskell の学習を難しいと感じさせる理由の一つだ。

この記事でもわかるように、literate programming がプログラミングの現場で使われることはあまりないような気がする。たった3行のプログラムを作成するのに 120 行もの文書作成が必要だからだ。

しかし、literete programming でプログラムを作成することは、プログラムがどのようにして生まれたのかという過程を明確にして、その概念を伝達するのには大きな力を発揮するだろう。データベースのような形で literate programming のプログラムが集積されたらそれは非常に大きな意味を持つのではないだろうか。


[PR]
by tnomura9 | 2016-10-25 04:30 | Haskell | Comments(0)

関数の再帰的定義

Haskell を使った literate programming はプログラムの学習には最適のような気がする。次の文書は関数の再帰的定義を説明した文書ファイルだが、ghci でプログラムとして実行できる。

関数の再帰的定義

Haskell では関数の再帰的定義が頻用される。再帰的定義は関数の定義をするのに、その関数自体を使うため、その考え方をマスターするのに少し時間がかかる。例えば階乗の計算を再帰関数を使わずに計算すると次のようになる。

n! = n * (n-1) * (n-2) * ... * 2 * 1

この計算は書ける数が順に一つずつ減っていっている。そのため上の式の見方を変えると、右辺の n の後の (n-1) * (n - 2) * ... * 2 * 1 は (n - 1)! と考えることができる。すなわち、

n! = n * (n - 1) !

である。これは左辺の n の関数 n! が右辺にも (n-1)! として表れているので、左辺の関数を右辺の同じ関数で定義していることになる。ただし引数の値が左辺は n で右辺は (n-1) であるので1減少している。

このように、もともと再帰的定義をしていない n の関数 n! もその成り立ちの構造に注目すると再帰的な定義を考えることができる。

このように関数の定義をその関数自身で定義する方法を再帰的定義と呼ぶが、このような定義を recursive case と呼ぶ。recursive case では右項の関数を次々により引数の値の小さいものに置き換えていくことができる。n! の場合で言えば次のようになる。

n! = n * (n-1)!
= n * (n-1) * (n-2)!
= n * (n-1) * (n-2) * (n-3)!
....
= n * (n-1) * (n-2) * ... * 2 * 1!

最後の 1! については 1! = 1 なので結局 n! の最初の定義と同じになる。recursive case をこのように展開していくうえで大切なのはどこかで関数の値が確定した値になるということだ。上の例でいえば、1! = 1 である。これを再帰的定期の base case という。recursive case のみの再帰的定義は無限の展開になってしまい関数の値が求められない。階乗の計算の場合は自明だが、再帰関数を自分で定義するときには base case を忘れてエラーになることはよくあるので注意が必要だ。

このように再帰的定義で関数を定義するときは recursive case と base case の二つで一組になっている。この階乗のプログラムを Haskell で定義すると次のようになる。

> factorial 1 = 1
> factorial n = n * factorial (n-1)

このプログラムでは1行目が base case である。2行目が recursive case のプログラムだ。Haskell でプログラムするときは、必ず base case を recursive case より先に記述する必要がある。式の評価は上から順に行われるので recursive case の方が先に書かれていると無限再帰呼び出しのエラーになる。

再帰呼び出しは関数の定義をするのに関数自身を使うという分かりにくい定義になるが、recursive case の右項の引数が、左項の引数より減少していること、また最後に確定的な値の base case が定義されていることに注目すれば理解しやすい。

この文書ファイルは ghci で load して factorial 関数を試すことができる。(Windows 10 のコマンドプロンプトで実行した。ファイル名の拡張子は .lhs にする必要がある。)

Prelude> :l recursion.lhs
[1 of 1] Compiling Main ( recursion.lhs, interpreted )
Ok, modules loaded: Main.
*Main> factorial 5
120


[PR]
by tnomura9 | 2016-10-24 14:52 | Haskell | Comments(0)

日常生活にプログラムを

Knuth 教授が提案した、literate program は小説や参考書のように読んで理解することのできるプログラムだ。それは単に読めば理解でき、暗号の解読が必要とならないプログラムだ。

アプリケーションなどの大規模なプログラム作成には、作成のためのオーバーヘッドが大きくて使われていないようだし。いつもこの方法でプログラムを書いている人はいないだろう。

しかし、この方法は日常生活のちょっとした問題を解決するのには便利なような気がする。例えば銀行ローンの利子の計算について、自分でも確認したいと思っても、その仕組みを学習するのは結構大変だ。さらに、その仕組みを理解したとして、それを実行してくれるプログラムを書いてみる必要もある。こんな時に literate programming が活用できるような気がする。

幸い、Haskell には literate programming を意識した bird style のソースコードを書くことができる。普通の文書のファイルに空行で前後を囲い、'>' の後に空白を置いて Haskell のプログラムを記述すると、その文書ファイルを Haskell のプログラムとして処理してくれる方法だ。この時、文章ファイルの拡張子は、.lhs (literate Haskell program) にする必要がある。

日常的に Haskell のプログラムを利用するというと抵抗があるかもしれないが、日常的な問題の解決のための小さなプログラムなら、C でも Ruby でも Shell Script でも書くことができる。もちろん Haskell でもだ。このような小さい問題のためのプログラムでは、どのプログラム言語を使うにしても、そう高度な知識は必要ないからだ。

このような日常生活のためのプログラミングを行うために想定しているのは、この literate program をエディターでテキストファイルに作成することだ。そうして、そのテキストファイルを ghci でロードして、その中の関数を使うというやり方だ。

この方式だと、入出力を ghci で行えるので、いちいち入出力プログラムを作成する必要がない。プログラムのデバッグも ghci 上で行えるなどの利点がある。前回の記事では、これを基礎代謝量の計算に使ってみたが、便利だった。

Haskell は決して新しいプログラムを学習するための実験的なプログラム言語だけではなくて、このように日常的なちょっとした問題を解決するためのプログラム言語でもある。

[PR]
by tnomura9 | 2016-10-24 08:31 | Haskell | Comments(0)

Haskell のバード・スタイル

Haskell のバード・スタイルで literate programming (読めばわかるプログラム)を書いて見た。基礎代謝量の説明と計算プログラムを同居させた。

基礎代謝率 BMR


基礎代謝率とは生命維持に必要な最小のエネルギー

必要量のこと。BMR(basal metabolic rate)。安静、

絶食状態での1日あたりのエネルギー消費と同量に

なる。主に体温の維持や、呼吸・循環機能、中枢

神経機能、最小限の緊張などを維持するために使

われるエネルギーの合計量である。


Harris-Benedict 式


男性 = 66.47 + 13.75 * 体重 + 5.0 * 身長 - 6.76 * 年齢

女性 = 655.10 + 9.56 * 体重 + 1.85 * 身長 - 4.68 * 年齢

体重:kg

身長:cm

年齢:才


> calmale w h a = round $ 66.47 + 13.75 * w + 5.0 * h - 6.76 * a

> calfemale w h a = round $ 655.10 + 9.56 * w + 1.85 * h - 4.68 * a


Mac のターミナルで実行することができた。Windows のコマンドプロンプトでは試していないが、日本語が Shift-JIS なので問題が起きるかもしれない。


Prelude> :l bmr.lhs

[1 of 1] Compiling Main ( bmr.lhs, interpreted )

Ok, modules loaded: Main.

*Main> calmale 86 178 62

1720


プログラム付きのメモというのは意外と便利だった。




[PR]
by tnomura9 | 2016-10-24 01:21 | Haskell | Comments(0)

Haskell の四捨五入

Haskell の四捨五入が round 関数だと思っていたら ??? な結果が出てきた。

Prelude Data.List> round 2.5
2
Prelude Data.List> round 3.5
4

これは四捨五入の方式が、HALF_EVEN という方式をとっているからだそうだ。

定食屋おろポンさんの記事を見て halfup 関数を作って見た。

Prelude Data.List> let halfup x = let (m,d) = properFraction x in if d < 0.5 then m else m + 1
Prelude Data.List> halfup 2.5
3
Prelude Data.List> halfup 3.5
4


[PR]
by tnomura9 | 2016-10-23 23:05 | Haskell | Comments(0)