多相的関数とパラメトリック多相

次の関数 thrd は3要素のタプルを引数にとって,3番目の要素を返す。

# let thrd (x, y, z) = z;;
val thrd : 'a * 'b * 'c -> 'c = <fun>

この, ‘a,’b,’c を型変数といい,thrd を適用するときには具体的にどんな型がきてもいい。つまり引数のタプルの各要素が,int であっても string であってもそれ以外のどんな型であってもいいってこと。

thrd のように型に関して抽象化された関数を多相的関数という。また,関数の型情報の一部をパラメータ化することによって発生する多相性をパラメトリック多相という。

# thrd ("foo", 2, 3.0);;
- : float = 3.
# thrd ('1', 2.0, (3, "three"));;
- : int * string = (3, "three")

演算子

前置・中置演算子も一種の関数なので,ふつうの関数と同じように定義できる。ただし,名前を括弧で囲むのと使える文字に制限がある。

# let (^-^) x y = x * 2 + y * 3;;
val ( ^-^ ) : int -> int -> int = <fun>
# 2 ^-^ 3;;
- : int = 13
# 5 ^-^ 9;;
- : int = 37

演算子に使える文字は次の通り。

前置演算子の1文字目:

! ? ~

中置演算子の1文字目:

= < > @ ^ | & + - * / $ %

2文字目:

! $ % * + - . / : < = > ? @ ^ | ~

中置演算子には上記のほかに次のキーワードが使える。

asr land lor lsl lsr lxor mod or !=

また,前置・中置とも次のキーワードは使えない。

#  '  (  )  ,  -> .  .. :  :: :> ;  ;; <- >]
?  ?? [  [< [> [| ]  _  `  {  {< |  |] }  ~

練習問題 3.14

p.67より。

実数上の関数 f に対して定積分 ¥normalsize¥int_a^b{f(x)}dx を近似計算する関数 integral f a b を定義しなさい。またこれを使って,¥normalsize¥int_0^{¥pi}{sin x}dx を計算しなさい。

近似の方法は台形近似。積分区間を100個の台形に分けて面積を合計する。

# let integral f a b =
let d = (b -. a) /. 100. in
let trapezoid x y z = (x +. y) *. z /. 2. in
let rec area i =
if i = 0. then 0.
else trapezoid (f (a +. d *. (i -. 1.))) (f (a +. d *. i)) d +. area (i -. 1.)
in
area 100.
;;
val integral : (float -> float) -> float -> float -> float = <fun>

あんまりきれいじゃないけどいいか。 + と +. を間違えてて時間がかかった。整数と実数で演算子が違うってのはどうもなれないな。

# let pi = 3.141592;;
val pi : float = 3.141592
# integra sin 0. pi;;
- : float = 1.9998355039556754

HTMLを整える

Hpricot とかで Web ページをスクレイピングするときに,対象のページを解析するのが結構めんどくさい。

ページによっては人間が読むのを想定しているとは思えない(というか大抵は想定してない)ような HTML で,読むのにひどく骨が折れる。なのでせめて見やすくなるように整形するスクリプトを書いてみた。

探せばちょうどいいツールがありそうだけど。

コマンドライン引数でファイル名か URL を指定する。

require 'rubygems'
require 'hpricot'
require 'open-uri'
def show_html(elem)
s = ""
elem.search("/*").each do |e|
if e.instance_of?(Hpricot::XMLDecl)
s << e.to_s
elsif e.instance_of?(Hpricot::DocType)
s << e.to_s
elsif e.instance_of?(Hpricot::Text)
s << e.to_s
elsif e.instance_of?(Hpricot::Elem)
s << "<#{e.name}"
e.attributes.each do |k, v|
s << " #{k}=#{v.inspect}"
end
s << ">\n"
s << show_html(e).gsub(/^/, "  ")
end
end
s
end
src = open(ARGV.shift, "r"){|f| f.read }
src.gsub!(/^\s+/, "")
doc = Hpricot(src)
print(show_html(doc).gsub(/^\s*\n/, ""))

出力(の一部)はこんな感じ。要素のネストをインデントで表現するようにした。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=euc-jp" http-equiv="Content-Type">
<meta content="text/css" http-equiv="Content-Style-Type">
<meta content="text/javascript" http-equiv="Content-Script-Type">
<title>
2007-12-18 - Haskell はスケるよ
<link href="/takatoh/" title="Haskell \244\317\245\271\245\261\244\353\244\350" rel="start">
<link href="/help" title="\245\330\245\353\245\327" rel="help">
<link href="/takatoh/20071216" title="\301\260\244\316\306\374" rel="prev">
<link href="/css/base.css" rel="stylesheet" media="all" type="text/css">
<link href="/headerstyle?color=lg" rel="stylesheet" media="all" type="text/css">
<link href="/theme/hatena_carving-blue/hatena_carving-blue.css" rel="stylesheet" media="all" type="text/css">
<link href="http://d.hatena.ne.jp/takatoh/rss" title="RSS" rel="alternate" type="application/rss+xml">
<link href="http://d.hatena.ne.jp/takatoh/rss2" title="RSS 2.0" rel="alternate" type="application/rss+xml">
<link href="http://d.hatena.ne.jp/takatoh/foaf" title="FOAF" rel="meta" type="application/rdf+xml">
<link href="http://d.hatena.ne.jp/takatoh/opensearch/diary.xml" title="Haskell \244\317\245\271\245\261\244\353\244\350\306\342\306\374\265\255\270\241\272\367" rel="search" type="application/ope nsearchdescription+xml">
<link href="http://d.hatena.ne.jp/takatoh/opensearch/archive.xml" title="Haskell \244\317\245\271\245\261\244\353\244\350\306\342\260\354\315\367\270\241\272\367" rel="search" type="application/o pensearchdescription+xml">
<link href="http://d.hatena.ne.jp/images/lg_favicon.ico" rel="shortcut icon">
<style type="text/css">
<link href="http://d.hatena.ne.jp/takatoh/mobile?date=20071218" rel="alternate" type="text/html" media="handheld">
<script type="text/javascript" src="http://d.hatena.ne.jp/js/prototype-1.4.0.js">
<script type="text/javascript" src="http://d.hatena.ne.jp/js/textinput_description.js">
<script type="text/javascript" src="/js/embed_movie_player.js">
<script type="text/javascript">
Event.observe(window, 'load', function() {
new TextInputDescription($('comment-username'), $('comment-form'), 'なまえ');
(以下略)

余計な空行は消してるつもりなのにどういうわけか残ってる。まぁ,目的は達成できてるようなのでいいか。

高階関数と匿名関数

もちろんある。

次の sum_of は 1~n までの整数それぞれに f を適用して合計を求める関数。

# let rec sum_of f n =
if n = 0 then 0 else f n + sum_of f (n-1);;
val sum_of : (int -> int) -> int -> int = <fun>

f は別のところで定義した関数でもいいし,その場限りの匿名関数(無名関数)でもいい。

匿名関数には fun 式を使う。

# sum_of (fun x -> x * x) 5;;
- : int = 55

ほぼ Haskell といっしょ。

練習問題 3.9

プログラミング in OCaml ~関数型プログラミングの基礎からGUI構築まで~ p.57より。

if式を OCaml の関数で表現することはできません。次の関数はそれを試みたものですが,fact 4 の計算の評価ステップを考え,なぜうまく計算できないのか説明しなさい。

# let cond (b, e1, e2) : int = if b then e1 else e2;;
val cond : bool * int * int -> int = <fun>
# let rec fact n = cond ( (n = 1), 1, n * fact (n-1) );;
val fact : int -> int = <fun>
# fact 4;;
Stack overflow during evaluation (looping recursion?).

式の評価戦略の説明のところ(pp.46-48)ではわかりにくいけど,要するに OCaml は関数の引数を先に評価する。fact 4 を評価することは cond ( (4=1), 1, 4 * fact (4-1) )を評価するってことで,fact 4 の値を確定する前に fact (4-1),つまり fact 3 の値を確定しなけりゃならない。同様に fact 3 の前に fact 2 を,fact 2 の前に fact 1 を,fact 1 の前に fact 0 を,fact 0 の前に fact (-1) を・・・・・・(以下略)となって,いつまでたっても値を確定できない。結局スタックオーバフローになった,というわけだ。

ここら辺は Haskell とは違うところだな。遅延評価する Haskell では何の問題も無く動く。

Prelude> let cond (b, e1, e2) = if b then e1 else e2
Prelude> let fact n = cond ((n == 1), 1, n * fact (n - 1))
Prelude> fact 4
24

練習問題 3.11 (2)

本文中で与えられた再帰的な定義で n個の中からm個を選ぶ場合の数 ¥left(¥begin{array}n¥¥m¥end{array}¥right) を求める関数 comb を定義せよ,という問題。再帰的な定義は本文(p.51)をみること(書くのが面倒なので)。

# let rec comb n m =
if m = 0 then 1
else if n = m then 1
else comb (n-1) m + comb (n-1) (m-1)
;;
val comb : int -> int -> int = <fun>
# comb 5 3;;
# comb 2 1;;
- : int = 2

練習問題 3.11 (3)

末尾再帰的関数を使ってフィボナッチ数を計算する iterfib

# let iterfib n =
let rec fibn (x, y) i =
if i = n then y
else fibn (y, x + y) (i+1)
in
fibn (0, 1) 1
;;
val iterfib : int -> int = <fun>
# iterfib 1;;
- : int = 1
# iterfib 2;;
- : int = 1
# iterfib 3;;
- : int = 2
# iterfib 4;;
- : int = 3
# iterfib 5;;
- : int = 5
# iterfib 6;;
- : int = 8

ちゃんと末尾再起になってるよね?

条件分岐

else は省略できない。

# let even n = if n mod 2 = 0 then true else false;;
val even : int -> bool = <fun>
# even 3;;
- : bool = false
# even 8;;
- : bool = true

ところで mod が中置演算子なのはへんな気分だ。

# 9 mod 3;;
- : int = 0
# (mod) 9 2;;
- : int = 1