愚直にループ(Python)
heights = []
for s in students:
if s.major is not Major.Math:
continue
if s.age < 20:
continue
if s.age >= 30:
continue
heights.append(s.height)
average_height = mean(heights)
● 状態変化使ってる
内包表記に書き換え
heights = list(map(lambda x: x.height, students))
even = list(filter(lambda x: x % 2 == 0, range(40)))
↓
heights = [x.height for x in students]
even = [x for x in range(40) if x % 2 == 0]
● 状態変化はないため、依然関数型といっていい
内包、楽しくなってくる
# 必修の教科書の値段を合計する
# 1000円以上の場合は大学から補助が出て
1000円になる
total_text_price = sum(
book.price if book.price < 1000 else 1000
for course in courses if course.mandatory
for book in course.textbooks if not i.have(book)
)
● ちょっとやりすぎ
Slide 33
Slide 33 text
対処法:
ループを書き、隠蔽する
Slide 34
Slide 34 text
ループを書いて、関数に状態変化を隠蔽
def total_price(courses, i):
result = 0
for course in courses:
if not course.mandatory:
continue
for book in course.textbooks:
if i.have(book):
continue
if book.price < 1000:
result += book.price
else:
result += 1000
return result
● ループを書いた方が読みやすい
● 状態変化は関数の中に隠蔽する
○ 関数の外から見れば状態変化してな
いので「外からは」関数型
■ 状態変化を考えなくて良い
■ 状態を外から触れない
○ 「中は」関数型じゃないが、短く端的な
らまあ別によいのでは
○ 参考:On Lisp 第3章
Slide 35
Slide 35 text
reduceも楽しくなりがち
Slide 36
Slide 36 text
functools.reduceとは
result = reduce(f, [a, b, c] , x)
x
a b c
f
f
f
acc = x
for e in [a, b, c]:
acc = f(acc, e)
result = acc
map/filterに並ぶ
関数型代表選手
Slide 37
Slide 37 text
複数の関数を途中経過をロギングしつつ連続適用
fs = [math_filter, tall_filter, grade_map]
x = students
results = [x]
for f in fs:
x = f(x)
results.append(x)
書き換え案1:区切り、名付ける+内包を使う
def is_20s(x):
return 20 <= x.age < 30
math = (s for s in students if s.major is Major.MATH)
math_20s = (s for s in students if is_20s(s))
average_height = mean(s.heights for s in math_20s)
Slide 44
Slide 44 text
書き換え案2:ループを書き、隠蔽する
def average_height(students):
heights = []
for s in students:
if s.major is not Major.Math:
continue
if not is_20s(s):
continue
heights.append(s.height)
return mean(heights)
● ループを関数に括り出した
だけ
● それでも関数型!
いろんな内包
# list
even = [i for i in range(30) if i % 2 == 0]
# dict
str_int = {str(i): i for i in range(30)}
# set
friends = {f for c in children for f in c.friends}
# gen
even_gen = (i for i in range(30) if i % 2 == 0)
Slide 52
Slide 52 text
オレオレ内包
class MyCollection(Sequence):
def __init__(self, _iter: Iterable):
self._list = list(_iter)
# ...
my_math = MyCollection(x for x in students if x.major is Major.MATH)
Slide 53
Slide 53 text
アンチパターン その4:
クロージャを濫用する
&
対処法:
オブジェクトと使い分ける
Slide 54
Slide 54 text
クロージャ+高階関数
def major_is(major):
# 下がクロージャ
def _ret(student):
return student.major is major
return _ret
● ランタイムに処理を生成することができる
Slide 55
Slide 55 text
クロージャ≒オブジェクト
class MajorCheck:
def __init__(self, major):
self.major == major
def __call__(self, student):
return student.major is self.major
● 以下のような等価な書き換えが可能
○ クロージャ→オブジェクト
○ 高階関数→クラス
● __call__を使えばオブジェクトを直接呼び出せる