ラベル 関数 の投稿を表示しています。 すべての投稿を表示
ラベル 関数 の投稿を表示しています。 すべての投稿を表示

2015年5月15日金曜日

LibreOffice Calc で英数字を均等割付したい場合、全角に変換する

LibreOffice Calc にはセル内にテキストを均等に割り付けることができる。しかし、半角英数のセルに適用しても変化なし。

例えば、以下のようにセルに入力した後、

  • 1 行目を選択 > 右クリック > セルの書式設定 > 配置 > テキストの配置 > 横位置

において、「均等割付」を選んだ。

SnapCrab_2015-05-12_22-16-35_No-0000

しかし、日本語の文字は設定通り変化するが、英数時は何も変化がなかった。

これに対して、どうしても「均等割付」をしたい場合、英数字を全角に変換すれば良い。

テキスト関数 - LibreOffice Help > JIS によると、

JIS 関数は、ASCII およびカタカナ文字を半角から全角に変換します。テキスト文字列を返します。

A2 セルに

=JIS(A1)

と入力し、右方向にコピー。

SnapCrab_2015-05-15_01-25-46_No-0000

これで英数字を均等に割り付けることができた。

2010年7月21日水曜日

Haskell のデータ構造と高階関数 - 総和・総乗から畳み込み関数へ

1. ウォーミングアップ用の課題を作成

久しぶりに Haskell を触ったら、毎度のごとく、頭から抜けてる。。 (o_ _)o~† 以前の記事を読み返しても、他人が書いたものにしか見えない。 (@_@;

どんな言語でも、一定期間、手を動かしてないと、基本的なことですら忘れる。

関数を定義する構文って def で始まるんだっけ?それとも function だったかな?

変数定義するとき、何を前に書くんだっけ?

など枚挙にいとまがない。だから、関数名なんて、色々なものがごちゃ混ぜになり、覚える気さえ起きない。

言語の依って立つパラダイムに馴染んでないと、そんな表層的な部分だけでなく、根本的なところでわからなくなる。覚えたと思ったことが、その片鱗さえ消え去っていることは日常茶飯事。そして、意気消沈。 1 歩進んでは 2,3 歩後退し、何かの間違えで、偶然 1 歩進める。そんな繰り返し。

当の Haskell は、相変わらず停滞している。どこまで何を理解したか、記憶が定かでない。さすがにこれではまずい。

対策として、思いついたのは計算ドリル。小学生のとき、機械的に計算を繰り返すことにより、計算に慣れた。それと同じように、ウォーミングアップ用の課題を用意しておき、記憶の底に沈み込んだ感覚を、少しでも回収できるよう、自分用の課題を用意しておくことにした。

 

2. 課題の内容

今回、課題としたのは、関数型プログラミングを学ぼうとした契機となった論文の一つより抽出。

の部分。この節は特に、関数の抽象化に馴染むための題材として打って付け。 Haskell に限らず、関数を引数として渡せたり、関数の返り値として関数を返すことができる言語、所謂、関数がファーストクラスオブジェクトな言語の練習課題として良いはず。

課題の内容は、

  1. リスト型を定義
  2. リストの要素に対して、「総和・総乗」を求める関数を定義 … (A)
  3. (A) の関数を元にして、抽象化した畳み込み関数を定義
  4. 上記の畳み込み関数を使って以下を定義
    • 総和・総乗
    • 値をコピーする関数
    • 要素に関数を適用する関数 ( map )
    • 特定の要素を抽出する関数 ( filter )

上記と同様な課題を、ツリーに対しても行う。

まとめると、

  1. 代数的データ型を定義し
  2. 簡単な計算を行ない
  3. そこから高階関数を導き
  4. 高階関数を使って、他の関数を定義する

 

3. リストで総和・総乗から、畳み込み関数まで

a. リストを代数的データ型で定義

まずはリストを定義する。 Haskell ではリストに対して特別な構文が用意されているが、ここでは代数的データ型として自分で定義する。 (cf. 6.1.3 リスト, Haskell の cons ) その方が、後々、別のデータ型を定義したときの参考になるため。

データコンストラクタは、以下の二つから成る。

  • 空のリストを表わす Nil
  • リスト型の先頭に要素を追加するデータコンストラクタ Cons
data List a = Nil 
            | Cons a (List a)
              deriving Show

これを用いて、例えば、数値 1 ~ 4 の要素を持つリストを作成。

list0 = Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))

 

b. リストで総和、総乗の定義

次に、リストの要素の総和を返す sumList 関数の定義をしてみる。

sumList Nil = 0
sumList (Cons x xs) = x + sumList xs

これに対して、総乗を返す関数 productList の定義は、

productList Nil = 1
productList (Cons x xs) = x * productList xs

先ほど定義した変数、リスト list0 に対して、上記の関数を適用すると、

main = do print $ sumList list0      -- 10 
          print $ productList list0  -- 24

 

c. リストで畳み込み関数

上記二つの関数の形は、よく似ている。違いは以下の具体的な値と関数。

  • リストがのときの値
  • 値を畳み込むための二項演算子

よく似た処理があるなら、それをまとめたい。そのためには、上記二つを「引数」として与える関数を考えれる。この関数を reduceList という名前にするなら、次のように定義できる。

reduceList f z Nil = z
reduceList f z (Cons x xs) = x `f` reduceList f z xs

元の関数から、引数を増やし、具体的な関数を、引数で与えた関数で置き換えるだけ。

この関数を用いて先ほどの総和と総乗を再定義すると、

sumList'     = reduceList (+) 0
productList' = reduceList (*) 1

 

d. 畳み込み関数を利用してリストを返す

総乗・総和の計算は、二項演算子を手段として、要素を一つの値に畳み込み集約する。一つの値に集約できるのは、二項演算子 (+), (*) の型を見ればわかる。

*Main> :t (+)
(+) :: (Num a) => a -> a -> a
*Main> :t (*)
(*) :: (Num a) => a -> a -> a

数値数値を足し合わせたり、掛け合わせると、結果が数値になるため。

ところで、リスト型を定義したときのデータコンストラクタは、

  • 要素
  • リスト

の二つを引数に与えると、リストが生成される二項演算子だった。データコンストラクタの型を確認すると、

*Main> :t Cons
Cons :: a -> List a -> List a

第 1 引数と第 2 引数の型が異なることがわかる。

データコンストラクタは、普通の関数と同じようなもの。(+), (*), Cons を中置記法で書いて比較すると、

*Main> 1 + 2
3
*Main> 1 * 2
2
*Main> 1 `Cons` Nil
Cons 1 Nil

データコンストラクタ Cons は、 (+) や (*) とはちょっと赴きが違うけれど、同じ仲間であることが、形の上ではっきりとする。ということは、総和・総乗を reduceList で定義したときと同じように Cons を reduceList へ与えても問題ない。

では、Cons を reduceList の第1引数として渡した場合、第2引数には何を渡せばいいのだろうか?

reduceList の計算のイメージを List folds as structural transformations を元に、第1引数に (+), 第2引数に 0 を与えた場合で考えたのが下図。

img07-14-2010[1]

これに対して上図の (+) の代わりに Cons を渡した場合、右端に置ける値は何か?

img07-14-2010[2]

先ほど Cons の型を確認したときのことを思い出せば、

*Main> :t Cons
Cons :: a -> List a -> List a

上記 `?’ の場所には List a 型の値を置けることが見てとれる。

img07-14-2010[3]

reduceList 関数において、これで問題ない理由は、Cons の 第2引数 と 返り値 の型が同じであるため。

ところで `?’ に Nil が来れば、与えたリストの要素をそのまま返す copyList 関数が定義できる。

copyList = reduceList Cons Nil

Nil ではなく Cons 1 (Cons 2 … のようなリストが来たなら、リストをリストに追加する appendList が定義できる。

appendList xs ys = reduceList Cons ys xs

もちろん、reduceList 関数を使わなくても、以下のように定義できる。

appendList' Nil xs         = xs
appendList' (Cons x xs) ys = Cons x (appendList' xs ys)

 

e. 要素に関数を適用する map 関数

なぜ関数プログラミングは重要か」 では、reduce 関数から map 関数を導くまで、丁寧に描かれている。ここでは、そこはすっ飛ばし、関数を合成する関数 compose を定義する。

compose f g h = f (g h)

この関数を使った、以下のような計算を考える。

compose Cons (* 2)

この式の型は、

*Main> :t compose Cons (* 2)
compose Cons (* 2) :: (Num t) => t -> List t -> List t

第 1 引数に数値、第 2 引数にリストを与えるとリストが返される。例えば、

*Main> (compose Cons (* 2)) 1 Nil
Cons 2 Nil

よって、compose の第2引数に関数を与える形にして、 reduceList の第1引数に与えることで map 関数が導かれる。

mapList f = reduceList (compose Cons f) Nil

mapList 関数を試してみる。

print $ mapList (* 2) list0 -- Cons 2 (Cons 4 (Cons 6 (Cons 8 Nil)))

もちろん、reduceList を使わなくても、map 関数は以下のように定義できる。

mapList' f Nil         = Nil
mapList' f (Cons x xs) = Cons (f x) (mapList' f xs)

 

f. 要素を抽出する filter 関数

map 関数が定義できたので、次は filter 関数。

ここでは、できるだけ Haskell で用意されている構文を使わないで書いてみたい。そこで、予め if を関数として書いておく。 (cf. if と真偽値 )

if' True  x _ = x
if' False _ y = y

これを用いて、要素を判定する述語を第1引数として与える関数 filterList を定義。 ( cf. 述語 )

filterList p = reduceList filter' Nil
    where
      filter' e l = if' (p e) (Cons e l) l

例えば、2 で割れる要素だけ抽出したい場合は、

print $ filterList (\x -> x `mod` 2 == 0) list0

こちらも map と同じく reduceList を使わずに定義するなら、

filterList' p Nil         = Nil
filterList' p (Cons x xs) = if' (p x) (Cons x filter') filter'
    where
      filter' = filterList' p xs

 

4. ツリーで総和・総乗から、畳み込み関数まで

リストの場合と同じように、ツリーでも以下の流れを踏襲して定義する。 ( filter だけちょっと違うけれど。)

  1. ツリー型を定義
  2. ツリーの要素に対して、「総和・総乗」を求める関数を定義 … (A)
  3. (A) の関数を元にして、抽象化した畳み込み関数を定義
  4. 上記の畳み込み関数を使って以下を定義
    • 「総和・総乗」を再定義
    •   値をコピーする関数を定
    • 要素に関数を定義する関数を定義 ( map )
  5. 特定の要素を抽出する関数を定義 ( filter )

 

a. ツリーを代数的データ型で定義

最初は、ツリーの代数的データ型を定義。

ここで、ツリーは各値を持つノードから成り、ノードはツリーのリストを持つとする。(「なぜ関数プログラミングは重要か」 に書かれていたのと同じ形にした。)

data Tree a = Node a (List (Tree a)) 

これを用いてツリーの値を作成してみる。

tree0 = Node 1 
        (Cons (Node 2 
               (Cons (Node 10 Nil) 
                (Cons (Node 20 Nil) 
                 Nil)))
         (Cons (Node 3 Nil) 
          (Cons (Node 4 Nil) 
           Nil)))

上記の表現だと、全体を把握しにくいので、Tree a 型を Show クラスのインスタンスにして、print 関数で出力したときに見やすく整形して出力されるようにしておく。

instance (Show a) => Show (Tree a) where
    show (Node x Nil) = show x
    show tree0        = show' tree0 0
        where
          show' (Node x trees) n = tabs   ++ 
                                   show x ++ 
                                   showTrees' trees (n+1)
              where
                tabs = replicate n '\t'

                showTrees' Nil n                = ""
                showTrees' (Cons tree1 trees) n = "\n"          ++ 
                                                  show' tree1 n ++ 
                                                  showTrees' trees n

これにより先ほどのツリーは、

*Main> tree0
1
	2
		10
		20
	3
	4

と表示される。

 

b. ツリーで総和、総乗の定義

リストの時と同じように、総和と総乗を定義する。

ノードが保持する値が数値であるとして、ツリー全体の「総和」を求めるには、

sumTree (Node x trees) = x + sumTree' trees
    where
      sumTree' Nil               = 0
      sumTree' (Cons tree trees) = sumTree tree + sumTree' trees

これに対して「総乗」は、

productTree (Node x trees) = x * productTree' trees
    where
      productTree' Nil               = 1
      productTree' (Cons tree trees) = productTree tree * productTree' trees

上記 2 つの関数を試してみる。

print $ sumTree tree0      -- 40
print $ productTree tree0  -- 4800

 

c. ツリーで畳み込み関数 (1)

リストの場合と同じく、畳み込み関数を定義する。ただし、ここでは、ツリーのリストを畳み込むための関数と、その結果となる値と、ツリーのノードを畳み込む関数を、同じもので定義してみる。

reduceTree f z (Node x trees) = x `f` reduceTree' f z trees
    where
      reduceTree' _ z Nil         = z
      reduceTree' f z (Cons t ts) = reduceTree f z t `f` reduceTree' f z ts

これを用いて総和と総乗を再定義。

print $ reduceTree (+) 0 tree0
print $ reduceTree (*) 1 tree0

 

d. ツリーで畳み込み関数 (2)

次に、ツリーの要素に対して何もせず、元のツリーの値を返す関数の定義を試みる。リストのときと同様に考え、reduceTree にツリーのデータコンストラクタを渡せば…

copyTree = reduceTree Node Nil

… としてはダメ。これでは、ツリーをコピーすることができない。理由は上記の定義で、敢えて関数を適用した後の値を書くとするなら、以下のようなツリーを構築しようとしているため。

tree0 = Node 1 
        (Node (Node 2 
               (Node (Node 10 Nil) 
                (Node (Node 20 Nil) 
                 Nil)))
         (Node (Node 3 Nil) 
          (Node (Node 4 Nil) 
           Nil)))

これは、明らかにツリーではない。

ツリーの型は、以下のように、

data Tree a = Node a (List (Tree a)) 

データコンストラクタ Node と、リストのデータコンストラクタから構成されている。よって reduceTree は高階関数として、もう一つデータコンストラクタを受け取る引数を用意しなければならない。

reduceTree f g z (Node x trees0) = x `f` reduceTree' f g z trees0
    where
      reduceTree' _ _ z Nil                 = z
      reduceTree' f g z (Cons tree1 trees1) = reduceTree  f g z tree1 `g` 
                                              reduceTree' f g z trees1

これを使い元のツリーをそのまま返す関数は、

copyTree = reduceTree Node Cons Nil

と定義できる。

総和・総乗を定義するなら、

print $ reduceTree (+) (+) 0 tree0
print $ reduceTree (*) (*) 1 tree0

 

e. 要素に関数を適用する map 関数

全てのノードの保持する値に関数を適用するには、ノードの値に関数を適用できるようにすればよい。よって、畳み込み関数の第1引数に、Node と与えた関数を合成したものを与える。

mapTree f = reduceTree (compose Node f) Cons Nil

試してみる。

*Main> mapTree (* 2) tree0
2
	4
		20
		40
	6
	8

 

f. filter (1) – ツリーからリストに変換した後に抽出する方法

では、要素を抽出するための filter はどうやって定義できるのだろう?うーん… (@_@;

ところで、「ふつうのHaskellプログラミング」(p122) には、Haskell で採用されている「遅延評価」について、次のように述べられている。

評価しなければならない値が存在するとき、実際の計算を値が必要になるまで行わない

( 遅延評価 - Wikipedia より )

また、Haskell で代表的なデータ型である「リスト」については、「インターフェイスを統一できる」 利点があるとして、次のように述べられている。

例えば巨大なツリー構造の全要素を順番にアクセスしたいと考えてみてください。Java で書くなら、ツリーに対するイテレータを用意するでしょう。一方Haskell の場合には、ツリーの要素すべてを含むリストを返す flatten という関数が使われます。

Haskell で用意されている Data.Tree 型を見ると、

flatten :: Tree a -> [a]

という関数が定義されており、返り値はリスト。

リストが遅延評価される Haskell ならば… ツリーの要素をすべて集める flatten 関数を使っても、本当にリストが生成されるのはリストを初めてたぐったときです。 flatten 関数を使ったリストが生成されることはありません。

(同上より)

Scheme:オブジェクト指向表現」 では、「ツリーの要素を辿って関数を適用するコード」が紹介さている。これに対しても、Haskell の遅延評価の観点から次のようなコメントがされている。

tree のトラバースは、要するに与えられた tree のすべてのノードを一列のリストに ならべればよいわけです。

Haskell プログラミング ∼ 純粋関数型言語への誘い∼」 (p.28) には 「Haskell のパターン」として、次のように書かれている。

代数データ型を定義する
そのデータ型を走査する高階関数を書く
高階関数に条件を指定し、必要な部分をリストにする
リストになれば、何でもあり

以上より、ツリーをリストにする関数 flatten を定義してみる。

flatten (Node x trees0) = Cons x (flatten' trees0)
    where
      flatten' Nil                 = Nil
      flatten' (Cons tree1 trees1) = flatten  tree1  `appendList` 
                                     flatten' trees1

この関数と、先ほどリストで定義したフィルタ関数を合成して、以下のように定義できる。

filterTree2List p = filterList p . flatten

この関数を使ってみる。

*Main> filterTree2List (\x -> x `mod` 2 == 0) tree0
Cons 2 (Cons 10 (Cons 20 (Cons 4 Nil)))

 

g. flatten の定義でリストの畳み込み関数を使う

ところで、Haskell で用意されている Data.Tree 型の flatten 関数の定義を見ると、畳み込み関数 foldr が使われている。

flatten :: Tree a -> [a]
flatten t = squish t []
  where squish (Node x ts) xs = x:Prelude.foldr squish xs ts

一見したところ、何でこれで定義できるのかわからなかった。。 (@_@; 特に squish 関数の定義の中で、畳み込み関数を使っており、その第 1 引数に squish を与えるところがイメージできない。

頭の中でイメージできないときは、図を描きながら考えてみる。まず、squish 関数の中の xs は、最初に空リストを与えられて呼出されていることより、リスト であることがわかる。 ts は Tree a 型のデータコンスラクタの定義より [Tree a] 。 よって、先ほどのように畳み込み関数をツリー状にして考えるなら、以下の図のようになる。

img07-21-2010[1]

最初に、右下端の二項演算子を適用する部分に注目。この二項演算子の引数は、squish の定義で見たように第1引数は Tree a 型の値で、第2引数は空リスト。

ところで、定義の 1 行目は以下のように記述されている。

flatten t = squish t []

squish は flatten と比較すると引数が一つ追加され、具体的な値が与えられていることより、

flatten  は汎用的な squish 関数の特殊なケース

であることが読み取れる。

しかし、なぜ flatten が squish の特殊ケースとして定義できるのだろう?上図の青い点線で囲ったところを意識しながら見るとわかるかもしれない。 flatten t が t をリストに変換することに対して、squish t [] は t をリストに変換したものに対して空リストを追加している。つまり、両者は同じ結果を返すことが明白 (?) QQQ

それにしても、どうして上記のような定義ができることを思い付いたのか理解できない。(+_+)

関数を定義するときに、

「この関数はどういった汎用的な関数の特殊ケースであるか?」

ということを常に考えていればいいのだろうか…。

この点に関して、とりあえず横に置いておき、先ほどの flatten を畳み込み関数を使って定義しなおしてみる。

flatten (Node x trees0) = Cons x (reduceList flatten' Nil trees0)
    where
      flatten' tree1 xs = flatten tree1 `appendList` xs

うーん…、動作はするけれど定義の仕方が異なる。 (+_+)

ここで flatten’ の第1引数をデータコンストラクタによるパターンマッチに変更し、上記の appendList 関数を使わず Cons で書いてみる。

flatten (Node x trees0) = Cons x (reduceList flatten' Nil trees0)
    where
      flatten' (Node x tree1) xs = Cons x (reduceList flatten' xs tree1)

上記の太字の部分を見比べると、

Cons x (reduceList flatten' Nil trees0)

は、

Cons x (reduceList flatten' xs tree1)

の特殊なケースであることがわかる。

よって、これをまとめ、最終的に以下のように定義ができるということだったのかな?

flatten (Node x trees0) =  flatten' tree0 Nil
    where
      flatten' (Node x tree1) xs = Cons x (reduceList flatten' xs tree1)

 

h. filter (2) - なるべくツリーの形を保って要素を抽出

先ほどはツリーをリストに変換してから要素を抽出した。別の抽出方法として、ある程度ツリーの形を保ったまま抽出ではできないだろうか?

例えば以下のようなツリーがあった場合、

1
	2
		10
		20
	3
	4

奇数の要素のみを抽出した結果は、

1
	3

偶数の要素を抽出した結果は、

2
	10
	20
4

というような関数を定義したい。

返される値は、ツリーが一つのこともあれば、ツリーのリストであることもある。関数名を filterTree とするなら、Tree a 型の値と要素を抽出するための述語を与えたら、Tree a 型の値のリストを返せばいいので

filterTree :: (a -> Bool) -> Tree a -> List (Tree a)

何も考えずにだらだらと定義すると…

filterTree :: (a -> Bool) -> Tree a -> List (Tree a)
filterTree p (Node x trees0) 
    | p x  == True = Cons (Node x trees') Nil
    | otherwise    = trees'
    where
      trees' = filterTree' trees0
          where
            filterTree' Nil                 = Nil
            filterTree' (Cons tree1 trees1) = filterTree p tree1 
                                              `appendList` 
                                              filterTree' trees1

せっかくなので、畳み込み関数で置き換えると、

filterTree p (Node x trees0) 
    | p x  == True = Cons (Node x trees') Nil
    | otherwise    = trees'
    where
      trees' = reduceList filter' Nil trees0
          where
            filter' tree xs = filterTree p tree `appendList` xs

ここから、Data.Tree 型の flatten 関数の定義 と同じように、より汎用的な関数の特殊な形という形式に定義を置き換えることはできるだろうか?

上記の tree’ をインライン化し、filter’ 関数の第1引数をデータコンストラクタによるパターンマッチに置き換えると次のようになる。

filterTree p (Node x trees0) 
    | p x  == True = Cons (Node x $ filterTree' trees0) Nil
    | otherwise    = filterTree' trees0
    where
      filterTree' = reduceList filter' Nil
          where
            filter' (Node x trees1) xs 
                | p x == True = Cons (Node x $ filterTree' trees1) xs
                | otherwise   = filterTree' trees1 `appendList` xs

よく見ると、上部の太字の定義は、下部の太字の定義の特殊なケースであることがわかる。

よって、これをまとめると以下のように書ける。

filterTree p trees0 = filter' trees0 Nil
    where
      filter' (Node x trees1) xs
          | p x  == True = Cons (Node x $ filterTree' trees1) xs
          | otherwise    = filterTree' trees1 `appendList` xs
          where
            filterTree' = reduceList filter' Nil

上記の関数を使う前に List a 型の導出インスタンス宣言を削除し、出力の方法を変える。

instance (Show a) => Show (List a) where
    show Nil          = ""
    show (Cons x Nil) = show x
    show (Cons x xs)  = show x ++ "\n" ++ show xs

試してみる。

*Main> filterTree (\x -> x `mod` 2 /= 0) tree0
1
	3
*Main> filterTree (\x -> x `mod` 2 == 0) tree0
2
	10
	20
4

 

関連記事

 

参考サイト

2009年7月20日月曜日

ブール型を返す冗長な関数

昔書いたコードを眺めていて、当時は違和感はなかったのだけれど、今見ると「あれ?」(@_@;) と思うものがあった。例えば、Python で「与えられた数が 10 未満であるかどうかを調べる関数」が次のように記述されていたとする。

def lessthan10(x):
    if (x < 10):
        return True
    else:
        return False

上記は次にように書けば 2 行で済む。

def lessthan10(x):
    return x < 10

ブール型を返す関数をチェックしてブール型を返すというパターンの関数において、他にも同じように冗長に書いているものがいくつかあった。以前は前者の書き方の方が見たときにパッと理解しやすく、後者はシンプルだけれど感覚的に何となく好きになれなかった。

上記をまとめると、

It is alway possible to replace the pattern if (E) {return true;} else {return false;} by the pattern return E;

(Simplifying Boolean Expressions and Conditionals より)

 

ところで、なぜ冗長だと気にかかるようになったかと考えてみると、Haskell で同じ関数を書くとするならセクションを使って

lessthan10 = (< 10)

面倒なので次のようには書かないし、

lessthan10 x = if x < 10 then True else False

ましてや次のようにも書かない。

lessthan10 x = case x < 10 of
                 True      -> True
                 otherwise -> False

lessthan10 x = case x of
                 x | x < 10 -> True
                 otherwise  -> False

嗜好が変わってきたのかなぁ~。言語が思考を規定すると言ったら大袈裟か。。 ^^; やっぱ習慣の引力?

 

参考

2008年9月17日水曜日

Python の関数はオブジェクト

1. 関数の引数に関数を渡す

「オブジェクト」が何であるかということは、とりあえず横に置いておいて...

Python では、「関数」を関数の引数に渡すことができる。

例えば、

  1. 関数を引数に取る、関数 hoge を定義し、
  2. hoge に関数 piyo を渡してみる。
def hoge(f):
    f()

def piyo():
    print "piyo"

hoge(piyo)

このように関数を引数として与えることに対して、関数型言語を学習する前は違和感を感じていた。しかし、今では関数を渡せない言語仕様の場合、

「何で関数だけ仲間はずれにするんだろう?」

と思うようになった。

 

2. Strategy パターンの代わりに使える

関数を渡して処理を進める方法は、関数を渡すことができない言語では、Strategy パターンを利用する。

Strategy パターンは、アプリケーションで使用されるアルゴリズムを動的に切り替える必要がある際に有用である。Strategy パターンはアルゴリズムのセットを定義する方法を提供し、これらを交換可能にすることを目的としている。

(Strategy パターン – Wikipedia より)

Strategy パターンを意識したコードを Python で書いてみる。

def test(a, f, b):
    print u"処理を開始します"
    print u"あれして、これして..."
    
    print u"ここからの独自処理は f に任せて..."
    result =  f(a, b)
    
    result += 1000
    print u"処理を終了します"
    return result

def add(a, b):
    print u"--- 足すぞ~ ---"
    return a + b

def subtract(a, b):
    print u"--- 引くぞ~ ---"
    return a - b

print test(10, add, 20)
print test(10, subtract, 20)

関数 test の引数 f は、具体的なストラテジーとなる関数を受け入れる。独自な処理をする部分だけ、渡された関数に処理を任せる。

上記を Java で実装する場合は、

  1. ストラテジーに相当する add, subtract をクラスにして、
  2. 同一のインターフェイスを implemnts し、
  3. 呼出し元から委譲されるようにする。

 

3. ファーストクラス オブジェクトの性質

第一級オブジェクト – Wikipedia」 によると、ファーストクラスオブジェクトの性質として、以下の 2 点が挙げれている。

  • プロシージャ関数のパラメータとして渡すことができる。
  • プロシージャや関数の戻り値として返すことができる。
  • 前者は、上例に相当する。

    後者は、関数をネストし、それを返すよう実装する。

     

    4. 関数のプロパティ

    Python では、関数は、

    呼び出し可能型 (callable type)

    に分類される。

    3.2 標準型の階層 に呼び出し可能型の説明がある。その内容に驚いた。 (@_@;

     

    a. 特殊属性

    一つは特殊属性。関数を定義すると、いくつかの属性が自動的に関数に与えられる。

    例えば、関数中にドキュメントを書いた場合、その文字列が保持される属性が存在する。

    http://www.python.jp/doc/2.4/ref/types.html

    3.2 標準型の階層 via kwout

    __module__  属性は、「関数が属しているモジュールについての情報」を持っている。この仕様は意外だった。なぜなら、「関数」に対して抱いていたイメージは、

    それ単体で無機質に存在している

    というものだったため。 Python のように、関数自身が自分の親 (のようなもの : モジュール) について知っているということに対して違和感を感じる。

    ところで、Python では、関数の中で  yield を使うとジェネレータが返される。関数は、

    ただ言われたことに対して答えを返す

    というよりは、

    クラスのように内部情報に基づいて、振る舞いを変える

    という印象を持つようになった。

    個人的に、このようなシンプルでない関数の仕様は好きになれない。

     

    b. 関数は自由にプロパティを付けられる

    Python の関数に対して、一番驚いた点は、

    関数オブジェクトはまた、任意の属性を設定したり取得したりできます。この機能は、例えば関数にメタデータを付与したい場合などに使えます。(同上より)

    関数に対して、勝手にプロパティをつけることができるの!? 試してみる。

    def hoge():
        pass
    hoge.a = u"ほげ"
    hoge.b = [1, 2, 3, 4, 5]
    
    def piyo():
        print "piyo"
    hoge.p = piyo
    
    print hoge.a
    for i in hoge.b:
        print i
    hoge.p()

    関数のプロパティに、文字列、リスト、関数などをつけてみた。結果は、

    ほげ
    1
    2
    3
    4
    5
    piyo

    おぉ~、ちゃんと表示された。JavaScript の関数オブジェクトみたい。

    2008年9月14日日曜日

    Python でネストした関数の定義と、呼び出し

    1. 関数の中の関数

    Python では、関数の中に、関数を定義することができる。

    例えば、ITmedia エンタープライズ:2.4への機能強化で広がるPythonの世界 (3/4) には、以下のような例が書かれている。

    def declareArgs(*argTypes):
     def checkArguments(func):
      assert func.func_code.co_argcount == len(argTypes)
      def wrapper(*args, **kwargs):
       ...

    ネストした関数を呼び出すには、関数の呼び出しで、順に引数を与えれば良い。

    7.5 関数定義 に、呼び出し方の例が書かれている。

    func = f1(arg)(f2(func))

     

    2. 関数のネストと、スコープ

    E. 用語集 によると、

    ネストされたスコープ (nested scope)
    ある文が何らかの定義に囲われているとき、定義の外で使われている変数をその文から参照できる機能です。例えば、ある関数が別の関数の中で定義されている場合、内側の関数は外側の関数中の変数を参照できますネストされたスコープは変数の参照だけができ、変数の代入はできないので注意してください。

    「内側の関数から、外側の変数を参照することはできても、変更できない」、ということに注意。

     

    3. ネストした関数の定義

    最初に、適当に関数 a を定義し、呼び出す

    def a():
        print "a"
    
    a()

    実行すると、結果は`a' と表示される。

    次に、関数 a の中に、関数 f をネストさせる。返り値は、ネストした関数 f とする。

    def a():
        def f():
            print "f"
        print "a"
        return f
    
    a()()

    結果は、

    a
    f

    上記の a()() による関数の呼出しが、どのように関数が実行されるか、順を追って考えてみる。

    まず、080914-007a() による関数呼び出し。

    1. 最初、関数 a の中の、ネストした関数 f が評価される。
    2. 次に、ネストした関数 f より下の文が実行される。
    3. 最後の return により、関数 f が返される。
    4. よって、a() の結果は、関数 f となる。 … (1)

    このため、a()() は、 関数 f を呼び出したことと同じになる。 … (2)

    関数 a は、print 文がネストした関数の後に書かれている。print 文を、ネストした関数の前に書いても、結果は同じ。ただし、関数 a() 内で、関数 f() を呼出す場合、呼出しよりも前にネストした関数を書く必要がある。そのため、ネストした関数は先頭に書いておく習慣にしておくのが良さげ。

    関数 a の呼び出し方を見て、JavaScript の無名関数の呼出し

    ()()

    を思い出した。

    これも、関数の呼び出し方としては、同じこと。

     

    4. ネストした関数において、ネストした関数を定義する

    080914-002ネストした関数で、更にネストした関数を定義することを試しておく。

    def a():
        def f():
            def g():
                print "g"
            print "f"
            return g
        print "a"
        return f
    
    a()()()

    慣れてないと読みづらい… ^^;

    結果は、

    a
    f
    g

     

    5. ファーストクラスオブジェクトとは

    7.5 関数定義 に、「一級のオブジェクト」という言葉が使われている。

    関数は一級の (first-class) オブジェクトです。関数定義内で``def'' 形式を実行すると、戻り値として返したり引き渡したりできるローカルな関数を定義します。ネストされた関数内で自由変数を使うと、def 文の入っている関数のローカル変数にアクセスすることができます。

    第一級オブジェクト - Wikipedia によると、

  • 無名のリテラルとして表現可能である。
  • 変数に格納可能である。
  • データ構造に格納可能である。
  • それ自体が独自に存在できる(名前とは独立している)。
  • 他のものとの等値性の比較が可能である。
  • プロシージャ関数のパラメータとして渡すことができる。
  • プロシージャや関数の戻り値として返すことができる。
  • 実行時に構築可能である。
  • ...
  • Python の関数は、ファーストクラスオブジェクトなので、「変数に格納できる」。先ほど定義した、関数 g の後に、次のように書いて実行する。

    g = a()()
    g()

    また、以下の記述も試してみる。

    f = a()
    g = f()
    g()

    実行すると、先ほどと同じ結果が得られる。

    a
    f
    g

    この動作から、Haskell の部分適用を連想した。

    例えば、2つの値を足し合わせる関数 sum を定義して、実行する。

    def sum(x, y):
        return x + y
    
    print sum(1, 2)    #=> 3

    これをネストした関数を使い、書き換える。

    def sum(x):
        def _sum(y):
            return x + y
        return _sum
    
    print sum(1)(2)    #=> 3

    関数を、変数に代入してから使うなら、

    add1 = sum(1)
    print add1(2)   #=> 3
    print add1(3)   #=> 4

     

    6. Callable

    ところで、定義した関数に対して、() を余分に付けると、次のようなエラーがでる。

    例えば、先ほど定義した関数で、

    def sum(x, y):
        return x + y
    
    print sum(1, 2)()

    と書いて、実行すると、

    TypeError: 'int' object is not callable

    callable の意味は、「呼出し可能」。エラーの内容は、「int オブジェクトは、呼び出し可能ではない」ということ。

    「呼び出し可能」は、特殊メソッドの中の一つとして、挙げられている。

    3.3.4 呼び出し可能オブジェクトをエミュレートする によると、

    __call__( self[, args...])

    インスタンスが関数として ``呼ばれた'' 際に呼び出されます; このメソッドが定義されている場合、x(arg1, arg2, ...)x.__call__(arg1, arg2, ...) を短く書いたものになります。

    では、Python において、「呼出し可能」とはどういう意味だろう?

     

    呼び出し可能型

    3.2 標準型の階層 には、Python における組み込みの型が一覧されている。これを見ると、 int, float, String などの型がどのように位置付けられているかがわかる。

    その中の一つに、「呼び出し可能型」が挙げられている。

    呼び出し可能型 (callable type)
    関数呼び出し操作 ( 5.3.4 節、``呼び出し (call)'' 参照) を行うことができる型です:

    「呼び出し可能型」の下に、「ユーザ定義関数」がある。

    ユーザ定義関数 (user-defined function)
    ユーザ定義関数オブジェクトは、関数定義を行うことで生成されます ( 7.5 節、``関数定義'' 参照)。関数は、仮引数 (formal parameter) リストと同じ数の要素が入った引数リストとともに呼び出されます。

    また、同階層に、「クラスインスタンス」がある。

    クラスインスタンス (class instance)
    クラスインスタンスは後で詳しく説明します。クラスインスタンスはクラスが __call__() メソッドを持っている場合にのみ呼び出すことができます; x(arguments) とすると、 x.__call__(arguments) 呼び出しを短く書けます。

    Python では、関数も型として定義されている。