引数のパターンマッチには match式をつかう。リストパターンは :: を使える。たとえば:
# let rec sum l = match l with [] -> 0 | hd::tl -> hd + sum tl ;; val sum : int list -> int = <fun> # sum [1;3;5;7;9];; - : int = 25
takatoh's blog – Learning programming languages.
引数のパターンマッチには match式をつかう。リストパターンは :: を使える。たとえば:
# let rec sum l = match l with [] -> 0 | hd::tl -> hd + sum tl ;; val sum : int list -> int = <fun> # sum [1;3;5;7;9];; - : int = 25
List.length はリストの長さを返す。
# List.length [1;2;3;4;5];; - : int = 5
実装してみる。
# List.length [1;2;3;4;5];; - : int = 5 # let rec length' l = match l with [] -> 0 | hd :: tl -> 1 + length' tl ;; val length' : 'a list -> int = <fun> # length' [1;2;3;4;5];; - : int = 5
List.append は2つのリストを連結する。
# List.append [1;2;3] [4;5;6];; - : int list = [1; 2; 3; 4; 5; 6]
実装してみる。
# let rec append' l1 l2 = match l1 with [] -> l2 | hd::tl -> hd :: append' tl l2 ;; val append' : 'a list -> 'a list -> 'a list = <fun> # append' [1;2;3] [4;5;6];; - : int list = [1; 2; 3; 4; 5; 6]
リストの連結は中置演算子 @ でもできる。
# ['a';'b';'c'] @ ['d';'e';'f'];; - : char list = ['a'; 'b'; 'c'; 'd'; 'e'; 'f']
reverse はリストを逆順にする。
# let rec reverse l = match l with [] -> [] | hd::tl -> (reverse tl) @ [hd] ;; val reverse : 'a list -> 'a list = <fun> # reverse [1;2;3;4;5];; - : int list = [5; 4; 3; 2; 1]
次の関数 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 !=
また,前置・中置とも次のキーワードは使えない。
# ' ( ) , -> . .. : :: :> ; ;; <- >] ? ?? [ [< [> [| ] _ ` { {< | |] } ~
p.67より。
実数上の関数 f に対して定積分 を近似計算する関数 integral f a b を定義しなさい。またこれを使って,
を計算しなさい。
近似の方法は台形近似。積分区間を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
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 といっしょ。
OCaml の関数はカリー化されているので,部分適用できる。
# let sum_of_twice = sum_of (fun x -> x * 2);; val sum_of_twice : int -> int = <fun> # sum_of_twice 5;; - : int = 30