Thursday, July 31, 2014

nodejs 外部コマンド実行

ここにC#で書いたVoiceroidTalker.exeという怪しげな実行ファイルがある。そして、私のSurfaceにはnode.jsが入っている。つまり…分かるね?

nodejsで外部コマンド

nodejs経由でゆかりさん達は喋ってくれるのか?

まあ外部コマンド叩くだけなのでできるんですけど。最近のjavascript(node)の万能っぷりが凄いことになってる。これ、Raspberryでも動いちゃうし(現在ビルド中まだ終わらにゃい。)

こんなコードを書いてゆかりさんとまきまきをしゃべらせてみた。

exec = require('child_process').exec

child = exec('VoiceroidTalker.exe', (err, stdout, stderr) ->
    if !err
        console.log "Success."
    else
        console.log "Fail."
        console.log err
)

リファレンス: http://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback

もう少し柔軟に書こうと思うとspawnらしい。

spawn = require('child_process').spawn
talk = spawn('VoiceroidTalker.exe')

talk.stdout.on('data', (data) ->
    console.log 'stdout:' + data
)

talk.stderr.on('data', (data) ->
    console.log 'stderr:' + data
)

talk.on('exit', (code) ->
    console.log 'exit code: ' + code
)

動くんだけど失敗したときのerrの中身がckw x NYAOSだと文字化けするんだよなぁ…。

VoiceroidTalkerがVoiceroid+のWindowハンドラ捕まえてボタン押してるだけなので、当然ながらVoiceroid+が起動していないと動かない(キリッ

一体何やってんだろうと自分でも思うorz

Written with [StackEdit](https://stackedit.io

Wednesday, July 30, 2014

Raspberry Pi - Dropbox-Uploader

今日はRaspberry PiでDropboxにアクセスできるようにしていく。

Raspberry x Dropbox

Dropbox-Uploader。既に何人かの方が言及している通り、DropboxはARMで動くRaspberryではビルドできない。(参考

そんな訳で代替案。今回使うDropbox-UploaderはBASHで実装されていてcurlさえあれば動くらしい。Dropboxがサードアプリ向けのインターフェース(REST?)を提供していて、それをbash - curlコマンドで叩くスクリプトって事なのかな。

インストール

早速インストールしていく。OSXからraspberry piにsshログインして作業。

pi@raspberrypi ~ $ mkdir projects
pi@raspberrypi ~ $ cd projects/
pi@raspberrypi ~/projects $ git clone https://github.com/andreafabrizi/Dropbox-Uploader.git
Cloning into 'Dropbox-Uploader'...
remote: Counting objects: 633, done.
remote: Total 633 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (633/633), 185.43 KiB | 83 KiB/s, done.
Resolving deltas: 100% (315/315), done.
pi@raspberrypi ~/projects $ cd Dropbox-Uploader/
pi@raspberrypi ~/projects/Dropbox-Uploader $ ./dropbox_uploader.sh 

 This is the first time you run this script.

 1) Open the following URL in your Browser, and log in using your account: https://www2.dropbox.com/developers/apps
 2) Click on "Create App", then select "Dropbox API app"
 3) Select "Files and datastores"
 4) Now go on with the configuration, choosing the app permissions and access restrictions to your DropBox folder
 5) Enter the "App Name" that you prefer (e.g. MyUploader290472397515860)

 Now, click on the "Create App" button.

 When your new App is successfully created, please type the
 App Key, App Secret and the Permission type shown in the confirmation page:

 # App key: XXXXXXXXXXXX
 # App secret: XXXXXXXXXXXX
 # Permission type, App folder or Full Dropbox [a/f]: X

 > App key is XXXXXXXXXXXX, App secret is XXXXXXXXXXXX and Access level is XXX Dropbox. Looks ok? [y/n]: y

 > Token request... OK

 Please open the following URL in your browser, and allow Dropbox Uploader
 to access your DropBox folder:

 --> https://www2.dropbox.com/1/oauth/authorize?oauth_token=XXXXXXXXXX

Press enter when done...

 > Access Token request... OK

 Setup completed!

./dropbox_uploader.shでは、ブラウザ(Dropbox開発者向けサイト)とコンソールを交互に行き来して設定を行う。指示通りにやっていけばまず迷わない。OAuth認証をやってるみたい。

インストール後、どこからでもdropbox_uploaderを実行できるようパスを通す。

pi@raspberrypi ~/dropbox/Voice $ vim /home/pi/.profile 
...
PATH="$PATH:$HOME/projects/Dropbox-Uploader"
pi@raspberrypi ~/dropbox/Voice $ source ~/.profile

さて、ここでDropboxの中身を覗いてみる。

pi@raspberrypi ~/dropbox/Voice $ dropbox_uploader.sh list
 > Listing "/"... DONE
 [D] 0      Music
 [D] 0      Voice
 [D] 0      カメラアップロード
 [D] 0      スクリーンショット
 [F] 223753 はじめに.pdf
 [D] 0      素材

リストアップできたぞ。続いてVoiceディレクトリだけをダウンロードしてみる。

pi@raspberrypi ~/dropbox $ dropbox_uploader.sh download /Voice ./
 > Downloading "/Voice" to "/home/pi/dropbox/Voice"... 
 > Creating local directory "/home/pi/dropbox/Voice"... DONE
 > Downloading "/Voice/makimaki-icoming.txt" to "/home/pi/dropbox/Voice/makimaki-icoming.txt"... DONE
 > Downloading "/Voice/makimaki-icoming.wav" to "/home/pi/dropbox/Voice/makimaki-icoming.wav"... DONE
 > Downloading "/Voice/yuakaary-invading.txt" to "/home/pi/dropbox/Voice/yuakaary-invading.txt"... DONE
 > Downloading "/Voice/yuakaary-invading.wav" to "/home/pi/dropbox/Voice/yuakaary-invading.wav"... DONE
 > Downloading "/Voice/zun-invading.txt" to "/home/pi/dropbox/Voice/zun-invading.txt"... DONE
 > Downloading "/Voice/zun-invading.wav" to "/home/pi/dropbox/Voice/zun-invading.wav"... DONE

ダウンロード成功。Windowsでボイスロイド3人娘のwavを作ってDropboxに上げ、それをDropbox-Uploaderでダウンロード(OSX ssh経由)という回りくどいことをしてみた。

余談

aplayしたらRaspberry Pi上でしゃべってくれたよ。デフォルトはHDMIに音声出力優先。将来的にスピーカに繋いだりする場合はアナログ出力優先とかにするのかな? (参考…ちょっと情報は古いね。もうDVI出力は無いし。)

pi@raspberrypi ~/dropbox $ aplay Voice/yuakaary-invading.wav 
Playing WAVE 'Voice/yuakaary-invading.wav' : Signed 16 bit Little Endian, Rate 22050 Hz, Mono

音声出力の優先設定。

#アナログ出力を優先する場合
$ amixer cset numid=3 1
#HDMI を優先する場合
$ amixer cset numid=3 2

Written with StackEdit.

Tuesday, July 29, 2014

Raspberry Pi B+ セットアップ

Raspbery Piが家にやってきたので早速動かしていくよ。

Raspbianインストール

イメージ作成

アクションカメラ用に買ったmicroSDカードを利用する。まずはイメージをダウンロード。
http://www.raspberrypi.org/downloads/
公式っぽいRASPBIANをまずは使ってみる。ダウンロード中にSDカードのフォーマットを行う。
OSX用ガイドのCOMMAND LINEを見ながら設定。
yukaarybox:~ yukaary$ diskutil list
/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *500.1 GB   disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:                  Apple_HFS Macintosh HD            499.2 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk1
   1:             Windows_FAT_32 NO NAME                 15.9 GB    disk1s1
SDカードを挿しているときの状態。15.9GBでファイルシステムはFAT32か。disk1にイメージを書き込めば良さげ。
yukaarybox:~ yukaary$ diskutil unmountDisk /dev/disk1
Unmount of all volumes on disk1 was successful
yukaarybox:raspberry yukaary$ sudo dd bs=1m if=2014-06-20-wheezy-raspbian.img of=/dev/disk1
Password:
2825+0 records in
2825+0 records out
2962227200 bytes transferred in 364.947778 secs (8116852 bytes/sec)
書き込み成功。micro SDカードを取り出してraspberryのカードスロットにセット、電源ON。

起動

意外とあっさり動いた。USBワイヤレスキーボードも認識してる。凄い。
最初はSetup画面に飛ばされたので以下の設定だけしておいた。
  1. Expand Filesystem
  2. Change User Password
Finishを選んで再起動。今度はCUIモードで起動した。デフォルトユーザ名「pi」と変更したパスワードを打ち込んでログイン成功。やったぜ。驚くほど簡単で拍子抜けしたでござる。

キーボードレイアウト変更

変更しておかないと設定ファイルを書き換える時に涙目になる。このサイトを参考にして設定したらなんとかなった。
root@raspberrypi# raspi-config
キーボードレイアウト設定。
Generic 105-key (intl) PC
Japanese
The default for the keyboard layout
No compose key
・<No>

再起動/シャットダウン

再起動はいいとして、どうやって電源落とすんだろう…いきなりブツンはまずいよね。
ここ見た感じsudo shutdown -h nowでいいのかなー。

無線LANの設定

どんどんいっちゃうぞ。何人かの方が既に言及しているけどWiFiドングルをUSBポートに挿すと、おそらくwlan0で認識される。このネットワークインターフェースを使ってWiMaxルータに接続する。
root@raspberrypi# vi /etc/network/interfaces
auto lo

iface lo inet loopback
iface eth0 inet dhcp

auto wlan0
allow-hotplug wlan0
iface wlan0 inet dhcp
wpa-conf /eta/wpa_supplicant/wpa_supplicant.conf
iface default inet dhcp
次にWiMaxルータのSSIDとパスワードを/eta/wpa_supplicant/wpa_supplicant.confに登録。
root@raspberrypi# wpa_passphrase <SSID> <PASSPHRASE> >> /eta/wpa_supplicant/wpa_supplicant.conf
<SSID><PASSPHRASE>は自分の環境に合わせる。ここまでやってRaspberryPiを再起動したら繋がる。

Wifiドングル

BUFFALO WLI-UC-GNM
無難に成功例が報告されているものにした。安いし。

リモートログイン

sshd入れようかなと思ってたらデフォルトで入ってるのね…。小ちゃいけどPCとして機能している。

今日はここまで

何とか使える環境になったので、これからサンプルプログラムとかを動かしていこうかな。
Written with StackEdit.

Haskell part5

part5は再帰について。

ここまでもサンプルコードにもよく出てきたようにHaskellでは割と重要な要素らしい。数学ではフィボナッチ数列なんかが再帰的な構造を持っている。

F(3) = F(2) + F(1)
     = (f(1) + f(0)) + f(1)

F(1)やF(0)のようなこれ以上入れ子にできない要素をedge conditionと呼ぶ。再帰を無限回繰り返さない意味で大事。Haskellではwhile/forループでどのように値を得るか記述する代わりに再帰的な表現でそれがどのようなものかを宣言する。哲学的だなorz

ぱねぇMaximun

再帰の一例としてmaximumを取り上げる。これはOrdタイプクラスのリストの中から最大のものを返す関数。これを再帰的に定義していく。

edge conditionは単一の要素しか持たないリストであり、この最大は選択の余地がない。次にもうちょっと長いリスト。headとtailに分けて考えると、tailの最大値よりheadの値が大きければheadが最大値。tailの最大値が大きければ、それが最大値。

Haskellで表現する。

mymaximum :: (Ord a) => [a] -> a
mymaximum [] = error "maximum of empty list."
mymaximum [x] = x
mymaximum (x:xs)
    | x > maxTail = x
    | otherwise = maxTail
    where maxTail = mymaximum xs

whereで再帰が定義されているのが地味に分かりにくい。パターンマッチのおかげで一切if/elseがなくクールだろ?みたいな内容。maxを使うともっとシンプルに書ける。

nicemaximum :: (Ord a) => [a] -> a
nicemaximum [] = error "my heart is empty."
nicemaximum [x] = x
nicemaximum (x:xs) = max x (nicemaximum xs)

replicate, take

指定した要素を指定した回数、列挙する関数。edge conditionは繰り返し数が0以下になった場合。この状態では空のリストを返せばいい。

myreplicate :: (Num i, Ord i) => i -> a -> [a]
myreplicate n x
    | n <= 0    = []
    | otherwise = x:myreplicate (n-1) x

実行サンプル。

*Main> myreplicate 3 5
[5,5,5]
*Main> myreplicate 0 5
[]
*Main> myreplicate (-3) 5
[]

負の数を与えるときは()で括らないと- 3て感じに解釈されて怒られる。

take。これはリストから指定した数分の要素を取得する関数。0個の要素を取り出した結果は[]、空行列からは何も取り出せないので結果は常に[]。この当たりがedge conditionかなー?

mytake :: (Num i, Ord i) => i -> [a] -> [a]
mytake n _
    | n <= 0     = []
mytake _ []      = []
mytake n (x:xs) = x : mytake (n-1) xs

先ほどのものよりedge conditionが2つある分、若干複雑。どちらも再帰の度に数・要素を1つ減らしていく。otherwiseが無いのでn <= 0に合致しない場合は次のパターンが評価される。

reverse。リストの並び順を逆にする関数。edge conditionは空行列。あとは(x:xs)を(xs ++ [x])という感じに並び替えていけばいい。

myreverse :: [a] -> [a]
myreverse [] = []
myreverse (x:xs) = myreverse xs ++ [x]

myreverse xsのxsはいずれ空になるからこの関数は止まる。

edge conditionが無い止まらないタイプの関数もhaskellでは実装できる。この手の関数を使うときは途中で再帰を打ち切るほうがいい。repeartを題材に考えてみる。

myrepeat :: a -> [a]
myrepeat x = x:myrepeat x

単純にmyrepeat 5とやると無限に5が並ぶリストを作り続ける。パターンに実際の数字を入れると、5:myrepeat 5>5:(5:myrepeat 5)> …。take 5 (myrepeat 5)とすれば5を5回リストに追加して止まる。本質的にはreplicate 5 3と変わらない。

zip。これは2つのリストの要素を順番にペア(タプル)にしたリストを生成する。edge conditionは2つのリストどちらかが空になった時になる。

myzip :: [a] -> [b] -> [(a, b)]
myzip _ [] = []
myzip [] _ = []
myzip (x:xs) (y:ys) = (x, y):myzip xs ys

実行結果。

*Main> myzip [1,4,5] ["yukarin", "makky"]
[(1,"yukarin"),(4,"makky")]

うむ。期待通りの結果。

elem。これはリストの中に指定した要素があるかどうかをチェックする関数。edge conditionはリストが空になった時。要素が見つからなかった事を意味するのでFalseを返せばいい。空になるまではリストから先頭要素を取り出して比較を繰り返す。

myelem :: (Eq a) => a -> [a] -> Bool
myelem a [] = False
myelem a (x:xs)
    | a == x    = True
    | otherwise = a `myelem` xs

実行結果。

*Main> myelem 3 [4,6,5,1]
False
*Main> myelem 3 [4,6,3,1]
True

うむ、動いたぜ。

Quick Sort

ソート可能なリストがあって、その要素のがOrdtypeclassだったとする。これを実際にソートしたい。クイックソートをhaskellで実装するとどうなるか?他の多くの言語と比べてもかなり短い行数で実装できる。

type signatureはquicksort :: (Ord a) => [a] -> [a]で決まりだ。edge conditionはリストが空になった時だ。メイン処理はコードを読んだほうが早い。

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
    let smaller = quicksort [a | a <- xs, a <= x]
        bigger  = quicksort [a | a <- xs, a > x]
    in  smaller ++ [x] ++ bigger

コードでアルゴリズムそのものを表現していてちょっと感動した。最後の再帰についてのまとめは流し読み。

  • 再帰にはパターンがある。
  • リストをheadとtailに分割する。
  • edge conditonはたいてい…
    • リストが空になったとき。
    • 木構造のリーフに到達したとき。

再帰的な方法で問題を解きたい場合は、再帰が適用されないケースを見つけるところから始める。それがedge conditionになる。

Written with StackEdit.

Sunday, July 27, 2014

Raspberry Pi

超小型PCが4000円くらいで買えるとな!?ってことでB+を発注中。
簡単な電子工作をやりたいので部品を色々探している。今欲しいのを適当に列挙。
  • TFTディスプレイ
  • スピーカー
  • 各種配線
  • はんだこて
他にも色々必要になりそうではある。
ここの記事はとても参考になりそう。まとめてくれた人に感謝。

気になったアプリケーション


  • sonic-pi-2 ... raspberry piで音を出すためのライブラリ?
    • http://www.raspberrypi.org/learning/sonic-pi-2-taster/

http://qiita.com/ie4/items/a4056d5abc8a7b0ce3c6
Written with StackEdit.

Saturday, July 26, 2014

Haskell part4

part4をやるぞ。

Syntax in Functions

関数でパターンマッチングができるとな。数値、文字、リスト、タプルなど何でもパターンマッチングの対象にできる。

lucky :: (Integral a) => a -> String
lucky 7 = "Yukarisan ga sawaideiru!"
lucky x = "Zannnen orz"

いわゆるswitch-case的なのが関数の構文レベルで存在するらしい。マッチングは上から順に実行される。xはefault的な位置づけ。catch-allって書いてある。引数aをIntegralで縛っているから文字や少数なんかは受け取れない。

サンプルもうひとつ。

voiceroids :: (Integral a) => a -> String
voiceroids 1 = "Yukarisan"
voiceroids 2 = "Makimaki"
voiceroids 3 = "ZunZun"
voiceroids 4 = "Futago"
voiceroids x = "Yukkuri"

ここでvoiceroids xを一番上に持っていくと、あらゆる結果がYukkuriになる。そりゃそうだ。

実用的な例として紹介されているのが前に出てきたproduct関数をパターンマッチングで実現した版。

factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n-1)

おお。いわゆる再帰的なコードが書けたぞ。

initial :: Char -> String
initial 'd' = "86"
initial 'y' = "Yukarinsan"
initial 'm' = "MakiMaki"
initial 'z' = "ZunZun"

文字によるマッチング。これ自体言うことはないが、catch-allパターンが無いので指定した4文字以外の文字が来るとクラッシュする。というわけでcatch-allは付けたほうがいい、という話。

タプルに対するパターンマッチング

addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors a b = (fst a + fst b, snd a + snd b)

2つのタプルを受け取り、タプルを返す関数。ベクトルの足し算。fst, sndとかださいだろ?もっとましな書き方があるぞってことで

addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors (x1, y1) (x2, y2) = (x1 + x2 , y1 + y2)

確かにこっちのが分かりやすい。でも定義のほうは->で、実装は=なのは何でだろな。

fst, sndはpairでしか使えない。なら作ればいいだろ!となり、

first :: (a, b, c) -> a
first (x, _, _) = x

second :: (a, b, c) -> b
second (_, y, _) = y

third :: (a, b, c) -> c
third (_, _, z) = z

_は、その部分は気にしてない(使うことがない)から省略って感じの意味らしい。

*Main> first ("yukaary", "craft", 3)
"yukaary"
*Main> second ("yukaary", "craft", 3)
"craft"
*Main> third ("yukaary", "craft", 3)
3

実行結果はまあ期待通り。今度はペアのほうが例外吐くんだけど今はまだいいや。

リストに対するパターンマッチング

*Main> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]
*Main> [a+b | (a,b) <- xs]
[4,7,6,8,11,4]

リストのこの定義は、リスト内の各ペアを足したリストを作るだけ。リストに対しては空リスト[]及び:を使ったパターンマッチが可能。

[1,2,3]1:2:3:[]とも表現できる。x:xsはリストの先頭要素をxに、残りをxsにバインドする。このパターンは長さ1以上のリストにしかマッチしない。’x:y:z:zs’みたいなパターンは長さ3以上のリストにしかマッチしない。

x:xsのパターンは再帰的な関数でよく使われるが、:は長さ1以上のリストにしかマッチしないよ。(長さ0の空リストはどうするんだろう…)

というわけでリストに対するパターンマッチングは分かったな。ではhead関数の定義を見てみる。

myhead :: [a] -> a
myhead [] = error "Fuxxin!! Can't call head on an empty list!"
myhead (x:_) = x

リストが空の場合はエラーを投げる。ので、あんまり使わないほうがいいんだって。タプルの時に出てきた_も使われている。

リストに対してもう少し有益な情報を出す関数を作る。

tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements:" ++ show x ++ " and " ++ show y
tell (x:y:_) = "The list is long. First is:" ++ show x ++ " second is:" ++ show
y

空のリストから順番にマッチングをかけている。パターンマッチングの順序関係はここではあんまり関係無さげ。長さ1のリストは(x:[])にしかマッチしないだろうし。(x:[])[x](x:y:[])[x,y]と書いてもいいが、(x:y:_)[]で囲えない。これは長さ2以上の全てのリストとマッチしてしまう。

リストの長さを出す自作関数はパターンマッチングの再帰で簡単に自作できる。

mylength :: (Num b) => [a] -> b
mylength [] = 0
mylength (_:xs) = 1 + mylength xs

まあこうなるよね。仮にhamという文字列にこのmylength関数を使った場合、以下のような評価が実行される。

  • 1 + mylength “am”
  • 1 + (1 + mylength “m”)
  • 1 + (1 + (1 + mylength “”))
  • 1 + (1 + (1 + 0))

続いてsumも実装してみる。mylengthの場合と実装は変わらない。

mysum :: (Num a) => [a] -> a
mysum [] = 0
mysum (x:xs) = x + mysum xs

パターンの中には全要素を参照する@という記法がある。xs@(x:y:ys)という表記において、xsはx:y:ysを参照する。とても単純な一例。

capital :: String -> String
capital "" = "Empty string."
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

これを実行すると、

*Main> capital "yukarin made TNT to bomb a Maki's house."
"The first letter of yukarin made TNT to bomb a Maki's house. is y"

allで入力した全文を取得できることを確認。

最後に++はパターンマッチに使用できない。仮に(xs ++ ys)と指定した場合、何がxsに該当するリスト、何がysに該当するリストか判別できない。(xs ++ [x,y,z])(xs ++ [x])なら意味があるだろうけど、リストの特性上これも無理。

ちょっと確認してみて無理だった。
let xs = [1,2,3]
xs ++ [4,5]
とかなら大丈夫だけど、関数の定義には使えないみたい。

Guaaaaaaaaaaaaaard!!

いわゆるガード条件。BMI(肥満指数だっけ)の判定する超簡単なGuardを使った関数。

chkbmi :: (RealFloat a) => a -> String
chkbmi bmi
    | bmi <= 18.5 = "looks like yukarisan, need a bit more weight."
    | bmi <= 25.0 = "looks like zunchan, the weight is in normal range."
    | bmi <= 30.0 = "looks like makimaki, need an exercise."
    | otherwise = "What's a fat pig, you are!!"

otherwiseは予約語みたいでotherwise = Trueと定義されているそうな。こいつが無い場合はcatch-allが動かずいずれかの条件に合致しない場合はエラーが投げられる。guard条件には計算式・関数を使用できる。

calcbmi :: (RealFloat a) => a -> a -> String
calcbmi weight height
    | weight / height ^ 2 <= 18.5 = "looks like yukarisan,"
    | weight / height ^ 2 <= 25.0 = "looks like zunchan."
    | weight / height ^ 2 <= 30.0 = "looks lile makimaki."
    | True = "You are a pig in capitalism."

実行。

"looks like zunchan."
*Main> calcbmi 45 1.70
"looks like yukarisan,"
*Main> calcbmi 120 1.70
"You are a pig in capitalism."
*Main> calcbmi 100 1.70
"You are a pig in capitalism."
*Main> calcbmi 80 1.70
"looks lile makimaki."

うん。動いている。最初heightの単位が分からんかった。

max, minなんかはguardを使って書ける。

mymax :: (Ord a) => a -> a -> a
mymax a b
    | a > b = a
    | otherwise = b

nicemax :: (Ord a) => a -> a -> a
nicemax a b | a > b = a | otherwise = b

nicemaxのほうが短くていいね。compareの自作版。

mycompare :: (Ord a) => a -> a -> Ordering
a `mycompare` b
    | a > b = GT
    | a == b = EQ
    | otherwise = LT

これが一番読みやすいとは言えそう。

*Main> mycompare 1 2
LT
*Main> 3 `mycompare` 2
GT

Where

calcbmi、weight / height ^ 2を3回も繰り返すのはナウくないって?whereがあるさ!って話。

nicebmi :: (RealFloat a) => a -> a -> String
nicebmi weight height
    | bmi <= 18.5 = "looks like yukarisan."
    | bmi <= 25.0 = "looks like zunchan."
    | bmi <= 30.0 = "looks like makimaki."
    | otherwise = "a ping in capitalism."
    where bmi = weight / height ^2

可読性の意味ではniceでも無い気がする。上→下→中と読まなきゃならない。こういうの見るとSQL思い出すな…。

goodbmi :: (RealFloat a) => a -> a -> String
goodbmi weight height
    | bmi <= yuka = yuka_msg
    | bmi <= zun  = "looks like zunchan."
    | bmi <= maki = "looks like makimaki."
    | otherwise = "a ping in capitalism."
    where bmi = weight / height ^2
          yuka = 18.5
          zun  = 25.0
          maki = 30.0
          yuka_msg = "looks like yukarisan."

whereで定義したものはguardの中でのみ参照可能。あと、where節に列挙する定義は整列しとかないとhaskellが同じブロックにあるかどうかで混乱するとか。

where節はもうちょっとエレガントに書く余地がある。

where bmi = weight / height ^2
      (yuka, zun, maki) = (18.5, 25.0, 30.0)

そろそろwhere節の(..)とタプルの(..)の区別について混乱してきた。多分、別物。

where節ではパターンマッチも使用できる。

initials :: String -> String -> String
initials "Initial" "d" = "DREAM...."
initials firstname lastname = [f] ++ "." ++ [l] ++ "."
    where (f:_) = firstname
          (l:_) = lastname

実行。

*Main> initials "Initial" "d"
"DREAM...."
*Main> initials "Yukari" "Yuzuki"
"Y.Y."

1個目はロータリー乗りとして変な使命感に駆られた。whereはパターンマッチのステートメント別に書けそうだね。

関数だって定義できちゃう的な例。

calcbmis :: (RealFloat a) => [(a, a)] -> [a]
calcbmis xs = [bmi w h | (w, h) <- xs]
    where bmi weight height = weight / height ^ 2

ペア(要素数2、タイプクラスRealFloatのタプル)の配列を受け取って、RealFloatの配列を返す関数。読み解くのがちょっと難易度上がったな…。
whereによるバインディングはネストができるらしい。どういう形でのネストなのかサンプルコードが欲しい

著者さんはそんなに肥満が心配なのかね。

Let it be

そろそろ頭抱えそう。whereとよく似たlet .. it ..という構文がある。letはあらゆる場所で変数を束縛するが、きわめて局所的でもある。

cylinder :: (RealFloat a) => a -> a ->a
cylinder r h =
    let sidearea = 2 * pi * r * h
        toparea = pi * r ^2
    in sidearea + 2 * toparea

書き方はlet <bindings> in <expressions>。letの部分で定義した名前は、その後に続くinの中で利用できる。whereでも似たような表現ができるが、違いは何か? letの場合、最初にbindingsを書いて、その後ろにそれを利用するexpressionを書き出す(whereは逆にパターンマッチングで使用する定義が後ろに来る)。letによるバインディングはwhereとは異なり、if-elseの同じ感覚で使える。

*Main> [if 5 > 3 then "Woo" else "Boo"]
["Woo"]
*Main> 4 * (if 10 > 5 then 10 else 0) + 2
42
*Main> 4 * (let a = 9 in a + 1) + 2
42

うむ。そうか。

*Main> [let square x = x * x in (square 5, square 3, square 2)]
[(25,9,4)]

ローカルスコープの関数を宣言する用途にも使える。

*Main> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey"; var = "there" in foo ++ var)
(6000000,"Heythere")

インラインで複数の変数をバインドした場合は;で区切る。

list comprehension ([x | x <- xs, x > 0]みたいの)にもletは使える。bmiを計算する関数をletを使って書き換えると次のようになる。

letcalcbmi :: (RealFloat a) => [(a, a)] -> [a]
letcalcbmi xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]

BMIが25以上の要素で構成するリストが得られる。(w, h) <- xsの部分でbmiを使用することはできない。この時点では定義されていない。また、list comprehension内ではinを使う必要はない。letで定義した名前が見える範囲があらかじめ定義されている。

ghci上でもinは省略できる。省略した場合、letで束縛した変数のスコープはghciセッション全域に渡る。

*Main> let zoot x y z = x * y + z
*Main> zoot 1 2 3
5
*Main> let boot x y z  = x * y + z in boot 4 5 6
26
*Main> boot 4 5 6

<interactive>:207:1:
    Not in scope: `boot'
    Perhaps you meant `zoot' (line 204)

なるほど。bootinの範囲でしか見えていないのか。

letはwhereとは異なり全ガード条件で利用可能なほどスコープが広くないから、うまく使い分けるといいらしい。

Case expressions

Haskellでのcase文はCやJavaより、もう一歩進んでいるらしい。変数の値を元に評価を行うだけでなく、パターンマッチングも利用する。関数定義の際のパラメータに対するパターンマッチングの話を思い出してくれたかね?実は同じことで内部的に変更可能だよ…多分こんな意味。

head1 :: [a] -> a
head1 [] = error "No head for empty lists!"
head1 (x:_) = x

head2 :: [a] -> a
head2 xs = case xs of [] -> error "No head for empty lists!"
                      (x:_) -> x

言いたいことは分かる。その上でcase使わなくてもよくない?と言いたい。

case expression of pattern -> result  
                   pattern -> result  
                   pattern -> result  
                   ...

構文はこんな感じ。パラメータに対するパターンマッチングとは異なる点として、caseはどこでも使える利点がある。サンプル。

desclist :: [a] -> String
desclist xs = "The list is " ++ case xs of [] -> "empty."
                                           [x] -> "a single."
                                           xs -> "a longer list."

出力が同じところをまず書き出して、その後は入力内容に従って文面を変える。where使って同様の関数を実装するとこうなる。

chklist :: [a] -> String
chklist xs = "The list is " ++ what xs
    where what [] = "empty"
          what [x] = "a single."
          what xs = "a longer list."

whereを使った場合は関数が入れ子になるのか。この程度ならわざわざ関数にするよりcaseのほうが使い勝手が良さそう。

Written with StackEdit.