ラベル リスト の投稿を表示しています。 すべての投稿を表示
ラベル リスト の投稿を表示しています。 すべての投稿を表示

2013年11月10日日曜日

Windows 7 でタスクバー プレビューを非表示にし、ウィンドウタイトルのリストを代わりに表示にしたい

1. Windows 7 のタスクバー プレビューを非表示にしたい

Windows 7 のタスクバーに表示されるウィンドウのプレビューは見にくい。
SnapCrab_No-0580

サムネイルは小さく、ウィンドウの中身を判別しにくい。そのため、サムネイルよりもウィンドウのタイトルで見分けることが多い。

サムネイルは横一列に並ぶので視線の移動が面倒くさい。 (+_+)

帯に短したすきに長し。

これならいっそのこと、プレビューを非表示にして、タイトルだけリスト表示にしたい。

 

Firefox でもタブをリスト表示

Firefox のアドオン Pano (tanakahisateru/Pano)は、開いているタブをリスト一覧で表示してくれる。

最近、タブグループのサムネイル表示よりも、タイトルのリスト表示に慣れてきた。

サムネイル表示は、対象の数が少ない場合、判別しやすい。しかし、対象の数が多くなると破綻する。

 

2. `7+ Taskbar Tweaker’ でタスクバーのプレビューを非表示にする

7+ Taskbar Tweaker を用いると、タスクバーのプレビューを非表示にし、ウィンドウタイトルのリスト表示に切り替えることができる。

 7+ Taskbar Tweaker を起動すると、タスクトレイにアイコン SnapCrab_No-0581 が表示される。

  • Hovering > List

を選択し、Settings ダイアログが表示されたら、Done ボタンを押す。

SnapCrab_No-0573

これにより、プレビュー表示が、

SnapCrab_No-0572

ウィンドウのリスト表示になる。

SnapCrab_No-0575

場合によって、リスト表示の方が見やすい。

タスクバーのリストが表示される速さを調整したい場合、以下を参照。

プレビュー表示に戻すには、同項目の Show thumbnail preview (default) を選択する。

ポータブル版も用意されているので、自分が所有している PC 以外でも利用しやすい。

Windows7&8のタスクバー設定を拡張できる『7+ Taskbar Tweaker』 : ライフハッカー[日本版] によると、

通常インストーラーの中にポータブル版も格納されています。USBメモリなどに入れての持ち運びも可能です。

 

関連記事

2013年2月18日月曜日

Scheme で car, cdr の組み合わせ

1. c ではじまり r で終わる関数

cons 関数によりペアが作られる。ペアの第1要素を得る関数は car 。第2要素を得る関数は cdr 。

連続して car, cdr をペアに適用するため関数が用意されている。関数名は `c’ ではじまり `r’ で終わる。

SnapCrab_NoName_2013-2-18_3-0-13_No-00

c と r の間に入れる文字は `a’ または `d’ 。それぞれ car, cdr に対応している。

Structure and Interpretation of Computer Programs によると、

9 Since nested applications of car and cdr are cumbersome to write, Lisp dialects provide abbreviations for them -- for instance,

The names of all such procedures start with c and end with r. Each a between them stands for a car operation and each d for a cdr operation, to be applied in the same order in which they appear in the name. The names car and cdr persist because simple combinations like cadr are pronounceable.

11.9 Pairs and lists

(caar pair)‌‌procedure

(cadr pair)‌‌procedure

[r6rs-Z-G-D-1.gif]

(cdddar pair)‌‌procedure

(cddddr pair)‌‌procedure

These procedures are compositions of car and cdr, where for example caddr could be defined by

(define caddr (lambda (x) (car (cdr (cdr x))))).

CAR and CDR - Wikipedia > Continued acceptance

car and cdr have the advantage that short compositions of the functions can be given short and more or less pronounceable names of the same form.

 

2. c と r の間にある a, d は適用する順を表す

例えば、

(define xs '(1 (20 21 22) 3 4 5))
(display (caadr xs))    ; 20

c と r の間にある文字列 `aad’ を見て、順に cdr, car, car を適用する。

SnapCrab_NoName_2013-2-18_3-24-32_No-00

c と r の間にある文字列が `dad’ の場合は、cdr, car, cdr と適用する。

(display (cdadr xs))    ; (21 22)

 

3. Racket に用意されている関数

Racket には car, cdr を 4つまで合成した関数が用意されている。

3.9 Pairs and Lists > 3.9.6 Pair Accessor Shorthands によると、

2つの組み合わせ。

3つの組み合わせ。

4つの組み合わせ。

2012年9月23日日曜日

Scheme のペアとリスト

1. Haskell の (:) コンスはリストを生成する

Haskell で、リストの先頭に要素を追加するには (:) 演算子を使う。

Prelude> 1:[2,3,4]
[1,2,3,4]

演算子 (:) は「コンス」と呼ばれる。この関数は、リストのデータコンストラクタとして定義されている。

The Haskell 98 Language Report > 6.1.3 リスト

data [a] = [] | a : [a] deriving (Eq, Ord)
リストは、二つの構成子をもつ代数的データ型であるが、3.7 節で述べたように特別な構文をもつ。最初 の構成子は空リストで、`[]' (「ニル」)と書く。二つ目の構成子 は、`:' (「コンス」)と書く。

(:) の型を確認する。

Prelude> :t (:)
(:) :: a -> [a] -> [a]

リストの要素は、同じ型でなければならない。そのため、次のような計算ではエラーとなる。

"Hoge":[2,3,4]

 

2. Lisp の cons はペアを生成する

Lisp にも同じ名前の cons 関数がある。ただし、Haskell の (:) とは異なる。

  1. cons により、2つの、値または値へのポインタを持つオブジェクトを生成する。
  2. cons により生成されたオブジェクトは「ペア」と呼ばれる。
  3. ペアの1番目の要素は car により、2番目の要素は cdr により参照する。

cons - Wikipedia, the free encyclopedia

… cons (play /ˈkɒnz/ or /ˈkɒns/) is a fundamental function in most dialects of the Lisp programming language. cons constructs memory objects which hold two values or pointers to values. These objects are referred to as (cons) cells, conses, non-atomic s-expressions ("NATSes"), or (cons) pairs.

Haskell のように、リスト型に対して定義されている関数ではない。

 

ペア

Lisp の cons によって生成される「ペア」とは、順序対 のこと。

順序対は、2つの要素で構成される。

Ordered pair – Wikipedia

… an ordered pair (a, b) is a pair of mathematical objects. In the ordered pair (a, b), the object a is called the first entry, and the object b the second entry of the pair.

cons によって生成されるのは、リストではない。

 

3. タプルでペアを表現できる

Haskell には、Data.Tuple 型が定義されている。この型を順序対として用いることができる。データコンストラクタは、リストと同じように特別な表記がある。

(,)

タプルとは、要素が順に並んでいるリストのこと。要素の数 n に応じて、n-tuple と呼ばれる。特に 2-tuple は順序対と言う。

Tuple – Wikipedia

… a tuple is an ordered list of elements. In set theory, an (ordered) n-tuple is a sequence (or ordered list) of n elements, where n is a positive integer.

The unique 0‑tuple is called the null tuple. A 1‑tuple is called a singleton, a 2‑tuple is called an ordered pair and a 3‑tuple is a triple or triplet.

タプルは、順序対をネストしたものと見なせる。

Tuples as nested ordered pairs

Another way of formalizing tuples is as nested ordered pairs:

  1. The 0-tuple (i.e. the empty tuple) is represented by the empty set \emptyset.
  2. An n-tuple, with n > 0, can be defined as an ordered pair of its first entry and an (n-1)-tuple (which contains the remaining entries when n > 1):
    (a_1, a_2, a_3, \ldots, a_n) = (a_1, (a_2, a_3, \ldots, a_n))

This definition can be applied recursively to the (n-1)-tuple:

(a_1, a_2, a_3, \ldots, a_n) = (a_1, (a_2, (a_3, (\ldots, (a_n, \emptyset)\ldots))))

 

4. Scheme と Haskell の比較

Data.Tuple に定義されている関数は、順序対に対して適用できる。

Lisp のペアに対する操作と名前を比較すると、

  Lisp Haskell
ペアを生成 cons (,)
1番目の要素を取得

car

fst
2番目の要素を取得 cdr snd

Haskell でタプルを生成し、上記の関数を適用する。

  print (1, 2)              -- (1,2)   print (1, "hoge")         -- (1,"hoge")
  print $ fst $ (1, 2)      -- 1
  print $ snd $ (1, "hoge") -- "hoge"

リストと違い、タプルの要素は異なる型でも良い。

次に、同じ操作を Scheme で書いてみる。Scheme は Lisp の一方言なので、上記と同じ関数が用意されている。

11.9 Pairs and lists

A pair is a compound structure with two fields called the car and cdr fields (for historical reasons). Pairs are created by the procedure cons. The car and cdr fields are accessed by the procedures car and cdr.

(display (cons 1 2))             ; {1 . 2}
(newline) 
(display (cons 1 "hoge"))        ; {1 . hoge}
(newline)  
(display (car (cons 1 2)))       ; 1
(newline)
(display (cdr (cons 1 "hoge"))) ; hoge
(newline)

 

5. Scheme のリストはペアからできている

リストの定義

Lisp 系の言語は、ペアで「リスト」を表現する。ペアという構造の上にリストが作られる。Haskell では、ペアを表現するためのタプルとリストは全く別の型。

Scheme において、リストは次のように定義されている。

  1. 空リスト
  2. ペアの cdr フィールドがリストであるもの

A list can be defined recursively as either the empty listor a pair whose cdr is a list. More precisely, the set of lists is defined as the smallest set X such that

  • The empty list is in X.

  • If list is in X, then any pair whose cdr field contains list is also in X.

(11.9 Pairs and lists より)

要素のない空リストは、特別に用意されており、

()

と表現する。

(display (cons 1 '()))          ;(1)
(newline)
(display (cons 1 (cons 2 '()))) ; (1 2)
(newline)

最初の例は、ペアの cdr フィールドに空リストがあるので、リストである。

次の例は、(cons 2 ‘()) がリスト。そのため、全体では

(cons 1 リスト)

という構造になるのでリスト。

 

リストはペアであるが、空リストはペアでない

リストは、ペアの特殊な形として作成される。そのため、リストはペアでもある。

対象がペアの場合真を返す述語 pair? と、リストである場合真を返す述語 list? を使って確かめてみる。

(define ls (cons 1 (cons 2 (cons 3 '()))))
(display (pair? ls))    ; #t
(display (list? ls))    ; #t

ただし、空リストはペアではない。

The empty listis a special object of its own type. It is not a pair. It has no elements and its length is zero.

また、述語 null? に対して唯一真を返すオブジェクト。

(null? obj)    procedure

Returns #t if obj is the empty list, #fotherwise.

(display (pair? '()))  ; #f
(display (list? '()))  ; #t
(display (null? '()))  ; #t

まとめると、

  1. リストはペアである。
  2. ただし、空リストはペアではない。
  3. 空リストは、null である特別なオブジェクト。

当初、この点に違和感を覚えた。なぜなら、ペアが保持している要素によって、可能な操作が異なる。そのため、要素の内容により、型が変化するように見えたから。

たとえば、リストの逆順を返す reverse 手続きはリストに適用できるが、ペアには適用できない。同じような手続きはライブラリにいくつか定義されている。

Haskell でたとえると、

  • (1, [])
  • (1, (2, [])

は、ペアであると同時にリストであり、この場合にのみ適用可能な関数がある、といった感じ。Scheme よりも Haskell を先に学んだので、Scheme のペアとリストの関係がしっくりこなかった。

 

ドット対

ところで、cons 関数で生成したペアを display 関数で表示すると、`.’(ドット)を用いて表される。要素が3つのときは、次のように表示される。

(display (cons 1 (cons 2 3)))    ; {1 2 . 3}

ネストされたペアは、ドットが省略された形。

11.9 Pairs and lists

A chain of pairs not ending in the empty list is called an improper list. Note that an improper list is not a list. The list and dotted notations can be combined to represent improper lists:

(a b c . d)

is equivalent to

(a . (b . (c . d)))

ドットで表現されたペアを、ソースコードにそのままの形で書き、実行するとエラーとなる。

(display (1 2 . 3))

括弧で囲まれた最初の要素は手続き、それ以降の要素は引数として解釈されるため。

Procedure calls

(<operator> <operand1> ...) syntax

A procedure call consists of expressions for the procedure to be called and the arguments to be passed to it, with enclosing parentheses.

これを回避するには、quote を用いる。

11.4.1 Quotation

Semantics: (quote <datum>) evaluates to the datum value represented by <datum> (see section 4.3).

quote の代わりに ‘ を使うこともできる。

(display (cons 1 (cons 2 3)))    ; {1 2 . 3}
(newline)
(display (quote (1 2 . 3)))
(newline)

 

6. Scheme の基本的な型

Scheme で操作する対象となるオブジェクト(値)は「型」を持つ。

1.1 Basic types

Scheme programs manipulate objects, which are also referred to as values. Scheme objects are organized into sets of values called types.

基本的な型に対応した述語が9つ定義されている。この手続きを用いて値の型を特定できる。

11.1 Base types

No object satisfies more than one of the following predicates:

boolean? pair?
symbol? number?
char? string?
vector? procedure?
null?

These predicates define the base types boolean, pair, symbol, number, char (or character), string, vector, and procedure. Moreover, the empty list is a special object of its own type.

Note that, although there is a separate boolean type, any Scheme value can be used as a boolean value for the purpose of a conditional test; see section 5.7.

上記には「ペア」と「空リスト」を特定する述語が挙げられている。しかし、リストに対応した述語は含まれていない。

述語 list? は、ペアとリストの解説で述べられている。

11.9 Pairs and lists

(list? obj)    procedure

Returns #t if obj is a list, #f otherwise. By definition, all lists are chains of pairs that have finite length and are terminated by the empty list.

このことからも、リストはペアの特殊な形であることが分かる。

 

関連記事

参考サイト

2011年3月8日火曜日

Scala の List クラスで map, filter, foldRight と シーケンス内包表記

1. Scala の覚えやすそうな点と、すぐに忘れそうな仕様

Scala に興味を持った理由は、「今からでも遅くない これから始めるScala(前編)」 で挙げられていた以下の特徴による。

  • オブジェクト指向と関数型言語の特徴を組み合わせています。Scalaでは、関数をオブジェクトとして扱うことができますし、高階関数も利用できるなど、関数型言語からさまざまな特徴を取り入れています。
  • Scalaは静的型付け言語ですが、型推論によって型の記述を省略することができます。変数や関数の宣言時に、コンパイラが型を推論してくれるため、型の指定をいちいち行わなくてもよいのです。
  • Trait(トレイト)という、実装を持つことができるインターフェイスを利用して、Mix-Inができます。
  • 暗黙の型変換(implict conversion)などを利用して、DSL(ドメイン固有言語,Domain-Specific Language)を作成しやすくなっています。
  • マルチコア時代を意識した、分散処理ライブラリ(Actorライブラリ)が用意されています。

(太文字は引用者による)

久しぶりに本を購入して読んでいる。しかし、いつもの如く頭に入らないし、覚えたものは身に付かず抜けていく。

Scala は、統一されていて覚えやすい仕様もあれば、「何だかごちゃごちゃしていて忘れること間違いなし」と感じるところもある。結果として見やすくなるコードのために覚えておくルールの多さに圧倒されてしまったり。。 (+_+)

Guido van Rossum は Neopythonic: Scala? で次のように述べている。

Perhaps my biggest disappointment in Scala is that they have so many rules to make it possible to write code that looks straightforward, while being anything but -- these rules all seem to have innumerable exceptions, making it hard to know when writing simple code will work and when not.

 

a. Haskell の関数と似た名前のメソッド名

Scala は関数型の特徴を持ち合わせている。しかも型推論をしてくれる。しかし、「Haskell ではこう書けるのに、何で Scala はだめなんだろう?」と疑問を抱くことがある。例えば、

  • メソッドの引数は必ず型を指定しないといけない。

Haskell で関数を定義するとき、引数の型を省略しても型を推論してくれる。

Prelude> let add x y = x + y

Scala では引数の型を指定しなくてはならないので面倒。 (+_+)

scala> def add(x:Int, y:Int) = x + y

この辺り、オブジェクト指向と折衷しているのが理由なのだろうか?スクリプトを書くのにお手軽に使えると期待していただけにちょっと残念。

ところで、Haskell との比較で言えば、関数名が似ているのはなぜだろう?例えば、Haskell でリスト操作に不可欠な、

  • map
  • filter
  • foldr

(cf. Prelude, Data.List)

各々の言語でこれに対応した関数、メソッドを比較すると、

Python map filter reduce 2.1 組み込み関数
Ruby collect, map find_all, select inject Enumerable
Scala map filter foldRight, :\ scala.collection.immutable.List 

これを見ると Haskell の関数名に Scala が一番似ていて、次に Python 。Ruby のメソッド名はかなり異なる。

scala.collection.immutable.List  には、上記以外にも類似したメソッドが見受けられる。もちろん、Python でも関数型プログラミングを意識したモジュールには似た名前の関数が定義されているけれど。

Scala と Haskell の言語設計者は何か関係でもあるんだろうか?

Scala の設計者である Martin Odersky が The Origins of Scala で次のように述べている。

Bill Venners: How did Scala come about? What is its history?

Martin Odersky: Towards the end of my stay in Zurich, around 1988/89, I became very fond of functional programming. So I stayed in research and eventually became a university professor in Karlsruhe, Germany. I initially worked on the more theoretical side of programming, on things like call-by-need lambda calculus. That work was done together with Phil Wadler, who at the time was at the University of Glasgow. One day, Phil told me that a wired-in assistant in his group had heard that there was a new language coming out, still in alpha stage, called Java. This assistant told Phil: "Look at this Java thing. It's portable. It has bytecode. It runs on the web. It has garbage collection. This thing is going to bury you. What are you going to do about it?" Phil said, well, maybe he's got a point there.

The answer was that Phil Wadler and I decided take some of the ideas from functional programming and move them into the Java space. That effort became a language called Pizza, which had three features from functional programming: generics, higher-order functions, and pattern matching. Pizza's initial distribution was in 1996, a year after Java came out. It was moderately successful in that it showed that one could implement functional language features on the JVM platform.

(太字斜体は引用者による)

一緒に仕事したという Philip Wadler と言えば、The Haskell 98 Language Report の Preface に名前があるように、Haskell の言語仕様を決める委員会の editor であり、Glasgow Haskell Compiler 発祥の University of Glasgow で教授をしていた人物。「モナドって何?」と調べていれば行き当たるであろう Monads for functional programming の著者。

… ということは、関数名が Haskell に似ていたとしても、あながち偶然でもないということだろうか?

 

b. 他によく似た名前の関数を持つ言語は?

Scala が影響を受けた言語について調べてみると、

Java, Pizza, Haskell … と順にある。

Fold (higher-order function) - Wikipedia には fold 系の関数の各言語の一覧が載せられている。この中で Scala の foldRight に似ている名前を持つ言語は Ocaml と Scheme 。それぞれ関数名は fold_right と fold-right 。

  • List より : map, filter, fold_right
Scheme は、

うーん、こちらの方が名前は似てるなぁ。結局、どの言語が一番影響を与えて関数名が決まったんだろう???

 

それはさておき、リスト操作における重要な関数である、

で練習した関数を Scala でも手を動かして覚えることに。 (+_+)

 

c. Meadow で Scala のスクリプトを実行

… とその前に、「オブジェクト指向プログラマが次に読む本 -Scalaで学ぶ関数脳入門」には Scala の実行方法として以下の 3 つが挙げられていた。

  • シェルで対話的に実行する
  • スクリプトを記述して実行する
  • プログラムを記述して実行する

対話的というのは REPL のことで、ちょっと試したいときに使う。それ以外は main メソッドを持つシングルトンオブジェクトを書いてコンパイルして実行しないといけない思っていた。その中間に位置する実行方法があったのに気づかず。。 (+_+)  これからはスクリプトとして書いて実行してみよう。

( cf. Getting Started with Scala の Script it! )

以前、エディタで Scala を実行する方法をいくつか見た。

やはり、Meadow を使っていこうかな。

方法は、

  1. REPL を使うときのようにエディタに書いて
  2. M-x scala-run-scala を実行
  3. C-c C-l でスクリプトを実行。

( cf. EmacsでつくるScala開発環境 前編(scala-mode) - tototoshiの日記 )

 

2. map

まずは、リストの要素を 2 倍にすることから。

Haskell で書くなら、

Prelude> let l = [1..5]
Prelude> map (\x –> x * 2) l

セクションを利用すると、

map (*2) l

 

a. map メソッドにおける型変数

map メソッドの型を見ると、(scala.collection.immutable.List)

def map [B] (f: (A) ⇒ B) : List[B]

メソッドで型変数 B が宣言されている。

なぜこの型変数 B が書かれているのかと言えば、List クラスの型変数 A

class List [+A] …

はリストの要素の型を表しており、map メソッドの引数で要素を変換する関数、

f: (A) ⇒ B

により、元の要素の型とは異なる型になる可能性があるため。(型変数の前に `+’ が付いている意味は横に置いておく。)

この辺り、Java で似たような実装を試したときを参考に。

とりあえず、map メソッドを使う分には詳細を理解する必要はない。

 

b. 無名関数と引数の型を省略

Scala で無名関数を定義するときは、以下のような形式。

(引数:型) => 式

例えば、数値が与えられたら 2 倍する関数を定義するなら、

val f = (x:Int) => x * 2

Haskell で

–>

と書くところを

=>

とすることに注意。また、面倒だけれど、無名関数の引数は型を指定しなければならない。

リストの要素を 2 倍するには、

val l = List(1,2,3,4,5)
l.map((x:Int) => x * 2)

(以下、上記変数 l を使う。)

map 関数の引数として無名関数を与えると、引数の型をしなくてもよい。

l.map((x) => x * 2)

これは変数 l が

List[Int]

という型であり、リストの要素が Int 型だとわかることを利用して、先ほどの map メソッドに当てはめると、

def map [B] (f: (Int) ⇒ B) : List[B]

となり、map メソッドの第1引数の関数の引数の型を推論できるため。

ついでに、引数が 1 つだと ( ) も省略できる。

l.map(x => x * 2)

 

c. 無名関数の引数の省略

また、Haskell のセクションに似た書き方も用意されている。無名関数の引数は、

_

で代用できるので、

l.map(_*2)

 

d. メソッド呼び出し . の省略

それから、メソッドの呼び出しである . も省略できる。

l map (_*2)

シンプルに書けたけれど、ここまでの道程が長い。。(+_+)

 

e. 変更可能なリストを利用する場合

ところで、上記のリストは変更不能な (imutable) リストを利用した。 Scala ではこの使用が推奨されている。

これに対して、変更可能なリストを使いたい場合は、

  • scala.collection.mutalble

を使う。

scala.collection.mutable.LinkedList の要素を追加するメソッド は

def :+ (elem: A) : LinkedList[A]

これを利用すると、

import scala.collection.mutable.LinkedList

var result = LinkedList[Int]()
for (e <- l){
  result = result.:+(e * 2)
}
println(result)

変更可能な変数は var を使って宣言。

result 変数を更新している箇所は、以下のように置き換えることができる。

result = result :+ e * 2

更に省略するなら、

result :+= e * 2

慣れないと、なぜこういう風に書けるのかわからなくなってしまうけれど。 ^^;

 

f. シーケンス内包表記

The Scala Language Specification には、

For Comprehension

と書かれており、A Tour of Scala では Sequence Comprehensions とある「シーケンス内包表記」。

List comprehension - Wikipedia に for と yield を使った説明がされていることより、

「リスト内包表記」

と同じような機能を持つようだ。これを使うなら、

for (e <- l) yield e * 2

Ruby の yield や、Python の yield と同じ予約語名なので頭の中が混乱してきた。。 (+_+)

 

3. filter

次に、filter メソッド。型を見ると、引数に述語 (predicate) を与えれば良いことがわかる。

(cf. scala.collection.immutable.List, Haskell の Prelude 散策 (2) - 述語)

def filter (p: (A) ⇒ Boolean) : List[A]

map メソッドと同じように徐々に省略できるところを省いていくと、

val l = List(1,2,3,4,5)
l.filter((x:Int) => x > 3)
l.filter((x) => x > 3)
l.filter(x => x > 3)
l.filter(_>3)
l filter (_>3)

シーケンス内包表記では、

for (e <- l; if e > 3) yield e

( ) を使わずに { } で書くと ; も省略できる。

for { e <- l
      if e > 3
} yield e

他の言語における for とちょっと違うので、混同しないように注意が必要。

 

4. foldRight

最後に、リストの値を集約する foldRight メソッド。(scala.collection.immutable.List)

def foldRight [B] (z: B)(f: (A, B) ⇒ B) : B

map, filter メソッドと違い引数が 2 つ。しかも、それぞれの引数が独立しているように見える括弧の書き方。

以下のようなシグニチャでないことに注意。

def foldRight [B] (z: B, f: (A, B) ⇒ B) : B

各々の引数を括弧で括る書き方は、Scala におけるカリー化の手段。引数のリストがあると言えばいいのかな?

カリー化とは、Chapter 8. Functional Programming in Scala によると、

… currying transforms a function that takes multiple parameters into a chain of functions, each taking a single parameter.

In Scala, curried functions are defined with multiple parameter lists,

def cat(s1: String)(s2: String) = s1 + s2

Of course, we could define more than two parameters on a curried function, if we like.

(太字は引用者による)

メソッドの引数が複数の括弧 ( )  で囲まれている様は、「複数の仮引数のリストを持っている」と言えば良いみたい。つまり、メソッドの引数の括弧が一つというのは、引数の書き方としては特殊な一例に過ぎないと。括弧ごとにメソッドの呼び出しを行うことができ、残りの引数を順に与えていき、最終的に全体の関数と同じ関数の連鎖が生まれる。

foldRight の場合で言えば、

リスト.foldRight(値)

により、foldRight の第 2 引数を受け取る関数が生成される。

では、foldRight を使って、要素を全て足す計算をしてみる。先ほどと同じく、徐々に省略した書き方をすると、

val l = List(1,2,3,4,5)
l.foldRight(0)((x:Int, y:Int) => x + y)
l.foldRight(0)((x,y) => x + y)
l.foldRight(0)(_+_)
(l foldRight 0)(_+_)

 

a. 別名 :\

foldRight と同じ機能のメソッドとして :\ がある。

def :\ [B] (z: B)(op: (A, B) ⇒ B) : B

なぜこんな形のメソッドがわざわざ定義してあるかと言えば、以下の foldr を説明した図を見れば一目瞭然。

http://en.wikipedia.org/wiki/Fold_(higher-order_function)

Fold (higher-order function) - Wikipedia, the free encyclopedia via kwout

上記と同じ計算をするなら、

l.:\(0)(_+_)
(l :\ 0)(_+_)

これまた慣れないと読みずらい。

 

b. foldLeft と /:

foldRight はリストを右 (末尾) から左 (先頭) へと計算を進めるのに対して、foldLeft は左 (先頭) から右 (末尾) へと計算を行なう。

(l foldRight 100)(_-_)    // (1-(2-(3-(4-(5-100))))) => -97 (l foldLeft 100)(_-_)     // (((((100-1)-2)-3)-4)-5) => 85

foldLeft の別名は /: 。foldRight の :\ とは対照的。

(l :\ 100)(_-_)
(100 /: l)(_-_)

: で終わるメソッド名は右結合なので、第1引数を左に置く。この例外的なルールにより、左から計算を畳み込んでいく様をコードから連想できるようになっている。

. によるメソッドの呼び出しで書くなら、

l./:(100)(_-_)

こういう例外的なことが存在することが良いことなのか悪いことなのか。うーん…

 

c. reduceRight とreduceLeft

Haskell では foldr の第2引数がなく、リストの要素のみに二項演算子を適用していく関数名は foldr1 。

Prelude> foldr1 (+) [1..5]
15

scala には foldRight1 というメソッドはなく、reduceRight が代わりに存在する。

l reduceRight (_+_)

当然ながら、対照的な reduceLeft もあり。

l reduceLeft (_+_)

 

d. map , filter を foldRight で書き直し

次に、map メソッドで要素を 2 倍した関数を foldRight で書き直したい。

Haskell では以下のように書ける。

Prelude> foldr ((:).(*2)) [] [1..5]
[2,4,6,8,10]

Scala では空リストを Nil で表現する。

scala.collection.immutable.Nil

object Nil extends List[Nothing] with Product

これを利用して、以下のように書けばいいのかと思いきや、エラーとなる。 (+_+)

(l foldRight Nil)(_*2::_)

エラーがでないようにするには、foldRight の第1引数の型を明示する。

(l foldRight List[Int]())(_*2::_)

理由は、Programming in Scala (p326) によると、

Generally, when tasked to infer the type parameters of a polymorphic method, the type inferencer consults the types of all value arguments in the first parameter list but no arguments beyond that.

(太字は引用者による)

メソッドの型推論は、仮引数のリストのうち、最初の実引数を調べることによって行われるようだ。

foldRight の型で言えば、

def foldRight [B] (z: B)(f: (A, B) ⇒ B) : B

引数 z を元に型推論が行われる。

Nil の型について :: メソッドを使って調べてみると、

scala> Nil
res0: scala.collection.immutable.Nil.type = List()

scala> 1::2::3::Nil
res1: List[Int] = List(1, 2, 3)

scala> "hoge"::"piyo"::"fuga"::Nil
res2: List[java.lang.String] = List(hoge, piyo, fuga)

:: メソッドに与える引数の型により、返り値の型が決まる。

ところで、foldRight の第1引数 z の型変数は B と書かれており、リストの要素から型を決めることができない。仮引数 z に Nil を与えても型が決まらない。だから、型を指定しなければならないということかな。

(l foldRight List[Int]())(_*2::_)

この場合の foldRight の型は以下のような感じ。

def foldRight(z: List[Int])(f: (Int, List[Int]) ⇒ List[Int]) : List[Int]

ごちゃごちゃしていて頭混乱するなぁ~。

 

map の定義

map 関数を foldRight メソッドを使って定義するなら、

def mymap[A,B](l: List[A], f:(A) => B) :List[B] = (l :\ List[B]())(f(_)::_)

Haskell では、型を明示しなくても推論してくれるのでシンプルに書ける。

mymap f = foldr ((:).f) []

 

filter の定義

filter メソッドを使った 3 以上要素を抽出する関数を :\ メソッドで書き直すなら、

(l :\ List[Int]())((x,y) => if (x>3) x::y else y)

filter メソッドを :\ メソッドを使って定義するなら、

def myfilter[A](l: List[A], p: (A) => Boolean) :List[A] = 
  (l :\ List[A]())((x,y) => if (p(x)) x::y else y)

Haskell なら、

myfilter p = foldr (\x y -> if p x then x:y else y) []

 

5. その他

a. Range で数値のリストを生成

ついでなので、範囲を表わす scala.collection.immutable.Range を使い、1 から 10 まで足してみる。

((1 to 10) :\ 0)(_+_)
(1 to 10) reduceRight (_+_)
(1 to 10) sum

 

b. ブロックを使って副作用

純粋関数型でないメリットは、変数の更新や副作用を簡単に書けること。( それがバグの温床にもなるけれど… )

ブロック { } を foldRight で用いると計算途中の値を出力できたりする。ブロックは最後の式の値が返されるので、以下のように書くと、計算途中の変数の値を出力可能。

((1 to 5) :\ 0){ (x,y) =>
  println(y)
  x + y
}

結果は、

0
5
9
12
14
res647: Int = 15

これは Ruby の inject メソッドを書くときに似ている。

puts (1..5).inject(0){ |x,y|
  puts x
  x + y
}

実行すると結果は、

0
1
3
6
10
15

あ~、Ruby の inject メソッドは左から右へと畳み込んでいくんだったか。つまり、Scala で書くと、

(0 /: (1 to 5)){ (x,y) =>
  println(x)
  x + y
}

2010年8月30日月曜日

Java で型変数を利用して Cons , Nil クラスによるリスト表現 – map, filter, foldr, foldl の実装

「Haskell でリストに対する関数を考えるときの視点」 のつづき

Haskell のリストに対応した Java クラス

前回は Haskell のリストに対応したクラスを定義し、リストの大きさを返すメソッド length を実装した。

08-29-20102

このクラスを使い、map, filter, foldr, foldl の実装を考える。

ここで予め Main クラスで利用するリストを作成しておく。以降この変数 list を使う。

IList<Integer> list = new Nil<Integer>().cons(4).cons(3).cons(2).cons(1);
System.out.println(list);    // 1 2 3 4 

 

累積変数を使った length メソッド

まずは、前回実装した length 関数を、末尾再帰で定義したときのような格好の実装を追加してみる。

Cons クラス...

    public int length(int acc) {
        return this.xs.length(acc + 1);
    }

累積変数 acc を導入し、そこへ要素の数を累積させる。

リストの末尾の Nil クラスでは、累積変数を返す。

    public int length(int acc) {
        return acc;
    }

Main クラスでこれを使ってみる。

        System.out.println(list.length(0));  // 4

 

map

次に、リストの要素に対して関数を適用する map 関数に相当するメソッドを定義する。

Haskell での map 関数の型は、

map :: (a -> b) -> [a] -> [b]

第1引数は、要素を別の型の値へ変換する関数。

Java では 「関数」 を関数の引数へ渡すことができないので、オブジェクトを代わりに渡し Strategy パターンで対応。オブジェクトの実装するインターフェイスを IUnaryOp として以下のように宣言し、利用するときはインターフェイスを実装した匿名クラスを作成する。

public interface IUnaryOp<A,B> {
    public B apply(A a);
}

apply 関数は、引数に A 型の値を与えると B 型の値が返ってくる。これは Haskell の map 関数の第1引数 (a –> b) に相当。

IList インターフェイスに map メソッドの宣言を追加。

    public <B> IList<B> map(IUnaryOp<A, B> f);

上記の型変数 B は、このインターフェイスを実装したクラスを生成するときではなく、このメソッドを使うときに決まるので、メソッドで型変数を宣言する。 ( cf. Java の型変数 とイレイジャ )

上記に伴ない Cons クラスで map メソッドを実装。

    public <B> IList<B> map(IUnaryOp<A, B> f) {
        return this.xs.map(f).cons(f.apply(this.x));
    }

Nil クラスにも map メソッドを実装。

    public <B> IList<B> map(IUnaryOp<A, B> f) {
        return new Nil<B>();
    }

これを Main クラスで使用する。

// map : 要素を 2 倍する
System.out.println(list.map(
        new IUnaryOp<Integer, Integer>() {
            public Integer apply(Integer a) {
                return a * 2;
            }
        }));     // 2 4 6 8

 

filter

filter 関数は述語を与え、リストから要素を抽出する関数。

Haskell の filter 関数の型は、

filter :: (a -> Bool) -> [a] -> [a]

第1引数は、ある型の値を渡すと Bool 型を返す述語。

先ほどの map メソッドのように、filter 関数の第1引数に相当する述語に対応したメソッドを持つオブジェクトのインターフェイスを IPredicate とし、メソッドを宣言。

public interface IPredicate<A> {
    public boolean apply(A a);
}

apply メソッドは、引数に型 A の値を与えると boolean が返る。これは Haskell の filter 関数の第1引数 (a –> Bool) に相当する。

IList インターフェイスに filter メソッドの宣言を追加。

    public IList<A> filter(IPredicate<A> p);

上記に伴ない Cons クラスで filter メソッドを実装。

    public IList<A> filter(IPredicate<A> p) {
        return p.apply(this.x) == true
                ? this.xs.filter(p).cons(this.x)
                : this.xs.filter(p);
    }

Nil クラスにも filter メソッドを実装。

    public IList<A> filter(IPredicate<A> p) {
        return new Nil<A>();
    }

Main クラスでこれを使う。

// filter : 偶数を抽出
System.out.println(list.filter(new IPredicate<Integer>() {
    public boolean apply(Integer p) {
        return p % 2 == 1;
    }
}));     // 1 3

 

foldr

forldr はリストの右側から二項演算子により特定の型の値に畳み込む関数。

Haskell の foldr の型は、

foldr :: (a -> b -> b) -> b -> [a] -> b

これまでと同様、foldr の第1引数の関数に相当するメソッドを持つオブジェクトを生成するためにインターフェイス IBinaryOp1 を宣言。今回は二項演算子なので、引数が一つ増える。

public interface IBinaryOp1<A, B> {
    public B apply(A a, B b);
}

apply メソッドの引数には型 A の値と型 B の値を与えると型 B の値が返る。これは Haskell の foldr 関数の第1引数の型 (a –> b –> b) に相当する。

IList インターフェイスに foldr メソッドの宣言を追加。このとき Haskell の foldr の型と比較しながら宣言を考えるとよい。

    public <B> B foldr(IBinaryOp1<A, B> f, B z);

上記に伴ない Cons クラスに foldr メソッドを実装。

    public <B> B foldr(IBinaryOp1<A, B> f, B z) {
        return f.apply(this.x, this.xs.foldr(f, z));
    }

Nil クラスにも foldr メソッドを実装。

    public <B> B foldr(IBinaryOp1<A, B> f, B z) {
        return z;
    }

これを Main クラスで使う。

// foldr : 要素を足し合わせる
System.out.println(list.foldr(new IBinaryOp1<Integer, Integer>() {
    public Integer apply(Integer a, Integer b) {
        return a + b;
    }
}, 0));                 // 10

// foldr : リストのコピー
System.out.println(list.foldr(
        new IBinaryOp1<Integer, IList>() {
            public IList apply(Integer a, IList b) {
                return b.cons(a);
            }
        },
        new Nil()));    // 1 2 3 4

 

foldl

foldr が右から畳み込むのに対して、foldl は左から畳み込む関数。

Haskell の foldl 関数の型は、

foldl :: (a -> b -> a) -> a -> [b] –> a

よく見ると foldr と型が少し違うのがわかる。

foldr :: (a -> b -> b) -> b -> [a] -> b

foldr のときと同様に、第1引数の関数に相当するメソッドを持つオブジェクトのインターフェイスを IBinaryOp2 とする。

public interface IBinaryOp2<A, B> {
    public A apply(A a, B b);
}

apply メソッドは引数型 A の値と型 B の値を与えると、型 A の値が返る。これは Haskell の foldl の第1引数 (a –> b –> a) に相当する。

IList インターフェイスに foldr メソッドを追加。ただし、foldr のときのように Haskell の型と完全に同じようになっていないことに注意。理由は、Cons クラスで実装したところを見るとわかる。

    public <B> B foldl(IBinaryOp2<B, A> f, B z);

上記に伴ない Cons クラスに foldr メソッドを実装。

    public <B> B foldl(IBinaryOp2<B, A> f, B z) {
        return this.xs.foldl(f, f.apply(z, this.x));
    }

特に第1引数が

IBinaryOp2<A, B>

ではなく、

IBinaryOp2<B, A>

であることに気をつける。

後者の場合、IBinaryOp2 インターフェイスに照らし合わせると、メソッド apply の型は次のようになる。

public B apply(B b, A a)

このように型変数名が変わっても本質的な意味は同じ。

しかし、これを Cons クラスのメソッドのシグニチャに用いると意味がそれぞれ異なる。理由は、Cons クラスが持つ型変数が A であり、この A と上記 apply メソッドの型変数名 A が一致するので、これによりメソッド全体において型変数 A の意味が定まるから。 (多分… ^^; 色々と試して動いたのがこれだったので、後付けで理由を考えた。)

Nil クラスにも foldr メソッドを定義。

    public <B> B foldl(IBinaryOp2<B, A> f, B z) {
        return z;
    }

これを使ってみる。

// foldl : 要素を足し合わせる
System.out.println(list.foldl(
        new IBinaryOp2<Integer, Integer>() {
            public Integer apply(Integer a, Integer b) {
                return a + b;
            }
        },
        0));       // 10

// foldl : 要素を掛け合せる
System.out.println(list.foldl(
        new IBinaryOp2<Integer, Integer>() {
            public Integer apply(Integer a, Integer b) {
                return a * b;
            }
        },
        1));       // 2

 

メソッドチェーン

最後に上記 map, filter, foldr でメソッドチェーン。

// メソッドチェーン : filter => map => foldr :
System.out.println(list.filter(new IPredicate<Integer>() {
    public boolean apply(Integer p) {
        return p % 2 == 0;     // 偶数を抽出
    }
}).map(new IUnaryOp<Integer, Integer>() {
    public Integer apply(Integer a) {
        return a * 3;          // 3 をかける
    }
}).foldr(new IBinaryOp1<Integer, Integer>() {
    public Integer apply(Integer a, Integer b) {
        return a * b;          // 掛け合わせる
    }
}, 1));     // 72

結論は、こんなゴチャゴチャしたコードは書きたくないということで。(+_+)

型を書くのが面倒なので型推論してくれぇ~。

てゆうか、これからの実用所はやっぱ Scala っすか ?

 

ここまでのコード

クラス図。(メソッドにおける型変数を無理矢理書いてしまったけれど。)

08-29-20103

 

関連記事

2010年5月31日月曜日

CSS で画像と文字を揃える

1. リストのマーカーとして画像を設定する

HTML でリストを書くには、ul, li 要素を用いる。

<ul id="list0">
	<li>hoge</li>
	<li>piyo</li>
	<li>fuga</li>
</ul>

このリストの項目の前に、マーカーとして画像star(star.png) を付けるには、CSS で指定する。

#list0 { 
	list-style-image: url("star.png"); 
	list-style-position: inside;
} 

img05-30-2010[1]

これにより、右図のように表示される。

 

リスト項目の背景画像を設定

マーカーの中心に文字の位置を合わせるには、マーカーをリスト項目の背景画像として設定する。

#list0 li { 
	list-style: none;
	background: url("star.png") no-repeat top left;
	padding: 15px 0px 15px 60px;
} 

img05-30-2010[2]

文字が真ん中に位置するように padding で上下の間隔を調整する。

 

2. 画像と文字を揃える

a. 要素を垂直方向に対して、中央で揃える

画像と文字の位置を、垂直方向に真ん中で揃えたい場合は vertical-align を使う。

<img src="star.png" style="vertical-align:middle;" />
<span >hoge piyo fuga</span>

 img05-30-2010[4]

 

b. 要素を水平方向に対して、中央に配置

水平方向に対して、要素を中央に配置したい場合は text-align を用いる。

text-align が適用される対象は、

Applies to: block-level elements, table cells and inline blocks

(16.2 Alignment: the 'text-align' property より)

div 要素の子要素として画像を配置。

<div style="text-align:center;">
  <img src="star.png" />
</div>

または、display と 要素をセンタリング するための margin の設定を併用し、

<img src="star.png" style="display:block; text-align:center; margin:auto;" />

 

c. text-align と vertical-align

しかし、不思議なのは要素を中央に配置するの指定が

text-align

であって、なぜ horizontal-align ではないのだろう?

vertical-align

と意味的に対になるなら horizontal-align の方が忘れにくいのに。

CSS1

仕様を見ると Cascading Style Sheets, level 1 では、Text properties 節に各々含まれている。

CSS2.1

Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification では、別々の節に分かれて説明されている。

 

参考サイト

2010年3月17日水曜日

Haskell の mapM_ – foldr と (>>) を意識して

1. mapM_ 関数は、どのように定義されているのか?

Haskell の print 関数で文字列を連続して出力させたい場合、次のように書く。

main = do print "hoge"
          print "piyo"
          print "fuga"

これを短かく書きたいなら、

main = mapM_ print ["hoge", "piyo", "fuga"]

最初、意味もわからず mapM_ の動作を覚えようとした。 ^^;

なぜこのように書けるのだろう?

 

2. map, fold 系の復習から

まずは map, fold 系の復習から。

map 関数は「各要素へ指定された関数 f を適用」する。

img02-11-2010[1]

リストの要素を 2 倍するなら、

*Main> map (*2) [0..5]
[0,2,4,6,8,10]

これに対して、fold 系 の関数は大雑把なイメージとして、「各要素の間に二項演算子 f を挟んだ後全体の計算をする」。

img02-11-2010[2]

リストの要素を足し合わせるには、

*Main> foldr (+) 0 [0..5]
15

リストの要素を 2 倍して、足し合わせるなら、関数合成を利用して、

*Main> foldr ((+).(*2)) 0 [0..5]
30

( cf. Haskell で関数合成 (2) )

 

3. map と foldr を使って、do 式を置き換える

最初の print 関数を 3 つ並べた例に戻る。

do 式 を使っていたので、これを Monad の (>>) で置き換えると、

main = print "hoge" >> print "piyo" >> print "fuga"

ところで、以下のリストに map 関数を適用した場合、

… map print ["hoge", "piyo", "fuga"]

次のリストが生成される。

[print “hoge”, print “piyo”, print “fuga”]

このリストの要素の間に二項演算子 (>>) を挿入すれば先ほどの形になる。

img03-17-2010[1]

よって、foldr を使えば、

main = foldr1 (>>) $ map print ["hoge", "piyo", "fuga"]

と書ける。

 

4. sequence_ と mapM_ 関数

以下のリストの型は [IO ()] 。

[print “hoge”, print “piyo”, print “fuga”]

IO はモナドの一種。

()

は、モナドな計算をつなげたときに、「前の計算の結果が、続く計算に必要ない」ことを示すために使われる印である「ユニット」。

 

sequence
  1. モナドな計算のリストを評価し、
  2. 結果を一つのリストにまとめ、
  3. モナドで包む関数

は sequence 。

sequence :: Monad m => [m a] -> m [a]

( cf. Haskell におけるモナドのサポート の直列化関数 )

img03-17-2010[2]

返される値に関心がない場合は、末尾にアンダーバー付きの関数。

sequence_ :: Monad m => [m a] -> m ()

これを用いて、上記 foldr1 関数を使った定義を、次のように書換えることができる。

main = sequence_ $ map print ["hoge", "piyo", "fuga"]
  1. 文字列のリストの各要素を print 関数で IO モナドで包み、
  2. 各モナドな計算を評価し、
  3. 結果を必要としない。

 

mapM

この sequence と map を使って定義してある関数が mapM 。

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

定義 を見ると、

mapM_ f as      =  sequence_ (map f as)
  1. map 関数に、モナドで包む関数を渡し、
  2. リストの各要素に適用した後、
  3. sequence 関数で、一つにまとめモナドで包んでいる

返される値に関心がない場合は、

mapM_ :: Monad m => (a -> m b) -> [a] -> m ()

よって、最終的には、

main = mapM_ print ["hoge", "piyo", "fuga"]

と書ける。あ~、ややこしい。。 (+_+)

 

5. State モナドの場合も同様に考える

上記は、IO モナドにおける mapM_ の適用例だった。今度は、同じモナドの仲間である、State モナドで考えてみる。

080329-004毎度おなじみの Person 型を以下のように定義。

data Person = Person { name :: String
                     , age  :: Int
                     } deriving Show

次に、Person 型の値のリストを保持する Group 型を定義。

data Group = Group [Person] deriving Show

Group 型の値に Person 型の値を追加する addPerson 関数を定義する。

addPerson0              :: Person -> Group -> Group
addPerson0 p (Group ps) = Group (ps++[p])

この関数を State モナド で包みたい。型が s –> (a, s) となるように調整する。

addPerson              :: Person -> Group -> ((), Group)
addPerson p (Group ps) = ((), Group (ps++[p]))

( cf. Haskell の State モナド (1) - 状態を模倣する )

State モナドを使うためには、State モジュールをインポートすることが必要。

import Control.Monad.State

addPerson 関数を使い、あるグループの値に 「太郎、次郎、花子」 を追加する関数 addPs0 を定義してみる。

addPs0 = do State $ addPerson $ Person "Tarou"  10
            State $ addPerson $ Person "Jirou"  20
            State $ addPerson $ Person "Hanako" 30

人が空っぽのグループ型の値を予め定義しておき、

g = Group []

addPs0 関数を試してみる。State モナドに包まれた関数を取り出すのは、runState なので、

*Main> runState addPs0 g
((),Group [Person {name = "Tarou", age = 10},Person {name = "Jirou", age = 20},Person {name = "Hanako", age = 30}])

(cf. jutememo's gist: 333569 — Gist )

 

mapM_ 関数へ

addPs0 関数の中身が不恰好なので、共通部分を抽出する。

  1. 「名前と年齢」のタプルを与えたら、
  2. Person 型の値を生成し、
  3. その値を addPerson 関数に与え、
  4. State モナドで包む

addPs’ 関数を定義。

addPs' (n,a) = State $ addPerson $ Person n a

関数の型を確認すると、

addPs' :: (String, Int) -> State Group ()

先ほどの print 関数の型と比較すると、

print :: (Show a) => a -> IO ()

包むモナドの種類が違うけれど、どちらも mapM_ の第1引数に与えることができる型。

mapM_ :: (Monad m) => (a -> m b) -> [a] -> m ()

addPs' を用いると、先ほどの定義が少しすっきりする。

addPs0 = do addPs' ("Tarou",10)
            addPs' ("Jirou",20)
            addPs' ("Hanko",30)

do 式を使わないなら、

addPs0 = addPs' ("Tarou",10) >> addPs' ("Jirou",20) >> addPs' ("Hanko",30)

先ほどと同じく foldr で置き換えるなら、

addPs0 = foldr1 (>>) $ map addPs' [("Tarou",10), ("Jirou",20), ("Hanko",30)]

sequence 関数を使うと、

addPs0 = sequence $ map addPs' [("Tarou",10), ("Jirou",20), ("Hanko",30)]

先ほどの print 関数を、mapM_ に渡したのを思い出し、addPs1 関数を定義。

addPs1 = mapM_ addPs' [("Tarou",10), ("Jirou",20), ("Hanko",30)]

これですっきりした。 ^^  しかし、いきなりこれ見たら、さっぱりわがんね。。 (+_+)

試しに使ってみる。State モナドの計算の結果のうち、状態の変化のみに関心があるので、今度は runState ではなく execState を使う。

*Main> execState addPs1 g
Group [Person {name = "Tarou", age = 10},Person {name = "Jirou", age = 20},Person {name = "Hanko", age = 30}]

( cf. jutememo's gist: 333569 — Gist )

 

ファイルからデータを読み込む

上記では、Person 型の値をハードコードしていた。これを、ファイルからデータを読み込むように変更してみる。

ファイルには次のようにデータが書かれていたとする。

"Tarou",10
"Jirou",20
"Hanko",30

文字列から代数的データ型に変換するには read 関数を使えばよい。

方法は、文字列から一度タプルにして読み込み、Person 型へと変換する。

文字列 → タプル → Person 型

文字列からタプルへと変換する関数は、

toTuple    :: String -> (String, Int)
toTuple cs = read line where line = '(' : cs ++ ")"

これを使うと、

main = do cs <- getContents
          print $ (execState $ mapM_ addPs' $ map toTuple $ lines cs) g

( cf. jutememo's gist: 333569 — Gist )

しかし、今はまだこれ見直しても何が書いてるかすぐにわからないなぁ。。 (@_@;

 

参考サイト

2010年2月2日火曜日

Haskell でリストから特定の要素を抽出する - 再帰的な定義、末尾再帰、filter, elem, partition, intersect

1. リストから特定の要素を抽出したい

例えば、ある村に、村人が 5 人住んでいたとする。

太郎、花子、次郎、三郎、明美

この村には、指名手配されている犯人がいる。指名手配されている犯人のリストは、以下の通り。

花子、三郎、四郎

これより、村にいる犯人を見つけたい。

 

2. 型の定義と、関数の型

最初に、「人」を表す Person 型を定義する。ここでは名前が一致することで、同じ人であるとする。

data Person = P { name :: String } deriving (Show, Eq)

村人のリストを定義。

ps = [P "Tarou", P "Hanako", P "Jiro", P "Saburou", P "Akemi"]

「指名手配されている犯人のリスト」と、「村人のリスト」が与えられたら、一致した人を、リストで返す関数を extract とする。関数の型は次の通り。

extract :: [Person] -> [Person] -> [Person]

 

3. filter 関数を使い、再帰的に定義

Haskell でリストから抽出するには、filter 関数を利用する。

具体的な処理のイメージは、

  1. 「指名手配されている犯人のリスト」から、一人ずつ取り出して、
  2. 「村人のリスト」と照合し、
  3. 一致するものがあれば返す。

リストの要素を順に見ていく処理は、再帰的な定義をする。

  1. 最初にパターンマッチにより、指名手配されている犯人のリストを、「先頭の要素」と「それよりも後ろの要素」に分解。
  2. 犯人リストの「先頭の要素」と、村人の「先頭の要素」が一致するか調べる。
extract (x:xs) ps = filter (== x) ps …

「先頭よりも後ろの要素」に対して、同じように一致しているか調べる。その結果を、上記の結果と結合して返す。

extract (x:xs) ps = filter (== x) ps ++ extract xs ps

ところで、指名手配されている犯人のリストを、パターンマッチで分解している。そのため、リストを分解しつくし、空リストになった場合を考える必要がある。よって、「指名手配のリストが空なら、一致する人はいない」という定義を加える。

extract []     _  = []

全体を示すと、

extract []     _  = []
extract (x:xs) ps = filter (== x) ps ++ extract xs ps

このような再帰的な定義の仕方を考えると、頭が混乱する。最近、少し慣れてきたかも。

 

4. 末尾再帰呼出しで定義

再帰的に定義できたので、「末尾再帰呼出し」の形に定義を変えてみる。

末尾再帰呼出しの形にするには、次の二つのことが必要。

  1. 結果を累積的に保持する役割を持つ引数を追加。
  2. 処理の最後で自分自身を呼出す。

最初に、結果を累積的に保持するための引数を、第 2 引数に置く。

extract' (x:xs) acc ps = 

次に、いきなり自分自身を呼出す。

extract' (x:xs) acc ps = extract' …

最初は acc が空だとして、「指名手配されている犯人のリストの先頭要素で、抽出できるか」試す。

extract' (x:xs) acc ps = extract' … (filter (== x) ps) … 

filter 関数で抽出した結果を acc に加える。

extract' (x:xs) acc ps = extract' … (acc ++ (filter (== x) ps)) … 

指名手配されている犯人のリストの残りも、同じように処理すればいいので、引数 acc に追加した結果を、引数に与えて再帰的な呼出しをする。

extract' (x:xs) acc ps = extract' xs (acc ++ (filter (== x) ps)) ps 

ただし、先ほどと同じく、パターンマッチで指名手配されている犯人のリストを分解している。そのため、これが空になった場合についても考えなければいけない。

引数 acc に結果が詰め込まれるはずなので、それをそのまま返す。

extract' []     acc _  = acc

全体を示す。

extract' []     acc _  = acc
extract' (x:xs) acc ps = extract' xs (acc ++ (filter (== x) ps)) ps 

 

関数をラップ

上記で定義した関数を使うには、引数 acc に空リストを与える。

extract' [P "Hanako", P "Saburou", P "Sirou"] [] ps

しかし、毎回、空リストを渡すのは面倒なので、extract’ 関数をラップする関数を定義する。

extract xs ps = extract' xs [] ps
    where
      extract' []     acc _  = acc
      extract' (x:xs) acc ps = extract' xs (acc ++ (filter (== x) ps)) ps 

これで関数のインターフェイスが、最初に定義した関数と同じになった。

末尾再帰呼出すメリットを簡単に言えば、再帰的な関数の呼出しが深くなり過ぎ、スタックオーバーフローしてしまったら、末尾再帰呼出しの形にして正格評価で対処できること。

 

5. 各々の要素が「特定のリストに含まれるか?」検査する方法

「リストの中にある要素が存在するかどうか?」というと、Python の シーケンス型 における `in’ を連想する。

print "Hanako" in ["Tarou", "Hanako", "Jiro"]      #=> True
print "Hanako" not in ["Tarou", "Hanako", "Jiro"]  #=> False

Haskell では elem, notElem 関数に相当する。

*Main> "Hanako" `elem` ["Tarou", "Hanako", "Jiro"]
True
*Main> "Hanako" `notElem` ["Tarou", "Hanako", "Jiro"]
False

問題に戻り、先ほどまでの定義を、

各々の村人を 「指名手配されている犯人のリスト」 から抽出できるか?

に変更。

extract xs ps = filter (\x -> x `elem` xs) ps

部分適用、セクションを利用すると、

extract xs = filter (`elem` xs)

 

抽出ではなく、リストを分割したい場合

抽出ではなく、村人を指名手配されている犯人と、そうでない人に分割したい場合は、Data.List の partition を使う。

import Data.List

extract xs = partition (`elem` xs)

 

6. 共通部分を抽出する方法

村人の集合と指名手配されている人の集合を図に描くと、求めたいのは二つの 集合の共通部分 ということになる。

img02-02-2010[1].png

Data.List には 集合的な操作 として intersect が定義されている。

import Data.List

extract = intersect

つまり、わざわざ自分で関数を定義する必要はなかった。

なぜこれをすぐに連想しなかったのだろう …  パタッ(o_ _)o~†

 

7. Data.Set を利用する

集合の操作をしたいなら、Data.Set モジュールを利用する。

リストと集合の変換は、

fromList  <=>  toList

共通部分 と 差 は、

それぞれ型を見ると、集合の要素が Ord のインスタンスできないといけない。そのため、Person 型に Ord を追加。

import Data.Set

data Person = P { name :: String } deriving (Show, Eq, Ord)

extract xs ps = toList $ xs' `intersection` ps'
    where
      xs' = fromList xs
      ps' = fromList ps

先ほどと同じように、リストを分割したいなら、

extract xs ps = ( toList $ xs' `intersection` ps', 
                  toList $ ps' `difference`   xs' )
    where
      xs' = fromList xs
      ps' = fromList ps

「要素に重複があってはならない」という制約がない限り、partition 使った方が楽かな。

2009年10月26日月曜日

Haskell のリスト定義はなぜあの形? - 素朴な解釈

空リストって何?

以前、「Haskell の cons (コンス) - リストを生成するための演算子 (:)」で、リストの定義とその構造を支えている cons について触れた。

これを書いたことすら忘れかけていた今日この頃、「やさしい Haskell 入門」におけるリストの定義を読んでいたら、また素朴な疑問がわいた。(@_@;)

リストも同様に簡単に定義できます。リストの場合は再帰的になっているのが面白いですね。
data [a]               = [] | a : [a]                  -- more pseudo-code
… []は空リストであり、:はリストの中置構築子です。つまり、 [1,2,3]は1:2:3:[]と同等であるはずです。(:は右結合性をもちます。) [] の型は [a] で、 : の型は a->[a]->[a] です。

(2.4 組み込みの型は特別な型ではない より)

疑問は次の 3 つ。

  • 要素が 0 の空リストってどんなイメージ?
  • 要素がないのになぜリスト?
  • なぜ空リストを想定しないとリストを定義できないの?

全て「空リスト」に関連している。

 

素朴に考える

リストと言われイメージするのは、要素が連なっているもののこと。

091025-005

最初に、最小のリストについて考える。もし、リストの要件として、「要素が連なっている」ことが必須であるならば、最小の連なりは二つの要素。

091025-006

これを代数的データ型で定義すると、

data List a = TwoElem a a

二つの要素を持つ List a 型の値を生成するには、

TwoElem 1 2

 

次に、3 つ以上の要素の連なりを表現するには再帰的な定義をする。

data List a = TwoElem a a
            | List (List a) a

3 つの要素を持つ List a 型の値を生成するには、

List (TwoElem 1 2) 3

4 つの要素の場合は、

List (List (TwoElem 1 2) 3) 4

 

見方を変える

ここで、リストを「要素の連なりである」という発想を忘れ、要素を入れる「入れ物」と考えるのであれば、要素が 1つ、要素が 0 の状態を想定したとしても不自然ではない。

data List a = TwoElem a a
            | List (List a) a
            | OneElem a
            | Empty

しかし、この定義は冗長。

なぜなら、リストに要素が一つの状態を表現するのに、

OneElem 1

と書けるけれど、

List Empty 1

で代用できる。よって、データコンストラクタ OneElem は必要ない。

同じように、リストに要素が二つある状態 TwoElem 1 2 は、

List (List Empty 1) 2

で代用できるので、データコンストラクタ TwoElem も不要。

結局残ったのは、

data List a = List (List a) a
            | Empty

上記の定義は、要素をリストの後ろに追加していくと解釈できる。要素をリストの後ろに追加していくことと、前に追加していくことは、リストの構造にとって本質的な意味を持たないので、以下のように定義したとしても同じ意味。

data List a = List a (List a)
            | Empty

以前書いたように、こちらの方が扱いが自然な感じがするので、多分この形になったのではないのかな?

 

関連サイト

関連記事