見出し画像

[書評] プログラミング文体練習 ~Pythonで学ぶ40のプログラミングスタイル

プログラミングパラダイム入門
この本では、Pythonを用いて関数型プログラミング、論理型プログラミング、オブジェクト指向プログラミングといった主要なプログラミングパラダイムの概念と特徴、さらにはそれぞれのプログラミングスタイルが説明されています。

具体的には、各パラダイムの特性やスタイル、その歴史、適用シーンが解説されていて、例示されたコードによって異なるコーディングスタイルの違いが具体的に示されます。関数型プログラミングの遅延評価や、オブジェクト指向のカプセル化、ニューラルネットワークといった、プログラミングの根本的な概念を習得することができます。

プログラミング言語を超えた普遍的な知識が身につくことで、あなたのコーディング能力は大幅に向上するでしょう。2,640円という価格は、プログラマーとしての視野を広げる投資として非常に価値があり、雑誌等では得られない情報を手に入れることができます。
目次及び章節のタイトルについては、「プログラミング文体練習」からの引用となります。

購入先 : O'Reilly Japan - プログラミング文体練習
原書: Exercises in Programming Style - 2nd Edition
著者名: Cristina Videira Lopes 著、菊池 彰 訳
出版日: 2023年06月
価格: Ebook 2,640円



第Ⅰ部 歴史的スタイル
1章 アセンブリ言語

1950年代のプログラミングスタイルであるアセンブリ言語に焦点を当てています。メモリが非常に限られた状況下でのプログラミングが課題となるとき、それにどのように対応するかを詳しく説明しています。アセンブリ言語の基本的な命令セット、レジスタ、フラグ、メモリの使い方などが詳細に解説されています。

  1. メモリ管理とは具体的には、メモリの各アドレスへの直接的なアクセスと操作を意味します。これはプログラムの効率を最大化するための重要な手段です。

重要な点/学んだこと

  1. メモリの制約が厳しい場合、データを効率的に処理するために1次記憶(メインメモリ)と2次記憶(補助記憶)の使い分けが重要であると学びました。

  2. アセンブリ言語では、低レベルの制御が可能であるため、細かいメモリ管理が可能になります。

  3. アセンブリ言語は機械語と直接対応しているため、コンピュータの仕組みを深く理解するのに役立ちます。これにより、プログラムの動作をより良く制御することが可能となります。


第Ⅰ部 歴史的スタイル 
2章 Forthで行こう:スタックマシン

スタックマシンと呼ばれるプログラミングモデルについて説明しています。スタックマシンでは、主要なデータ構造としてスタックが使われ、すべての演算はスタック上のデータに対して行われます。Forth言語がスタックマシンの代表例として紹介されています。スタックへのpush/pop、Forthの後置記法、ヒープメモリ、ユーザ定義の処理など、スタックマシンの概念が詳述されています。

  1. スタックマシンでは、主要なデータ構造としてスタックが使われ、すべての演算はスタック上のデータに対して行われます。

  2. Forthはスタックマシンに加えて、手続き(Forthでは「ワード(word)」と呼ばれる)の定義もサポートしています。

重要な点/学んだこと

  1. スタックというデータ構造と、それを中心とした計算モデルであるスタックマシンについて学びました。

  2. Forthなどのスタックマシン言語は、スタック操作を中心にプログラミングを行うスタイルです。

  3. スタックマシンではメモリ管理が重要で、スタックとヒープメモリの使い分けが必要です。

サンプル  以降、サンプルは[書評] プログラミング文体練習のオリジナルとなります。

# スタック操作

stack = [] 

stack.append(1) # push
print(stack) # [1]
x = stack.pop() # pop
print(x) # 1

# 後置記法のシミュレーション
stack.append(2) # push
stack.append(3) # push
result = stack.pop() + stack.pop() # pop and add
stack.append(result) # push
print(stack) # [5]

# ワードの定義
def add(a, b):
  return a + b
  
print(add(2, 3)) # 5


第Ⅰ部 歴史的スタイル 
3章 配列プログラミング:ベクトル演算

配列を主要なデータ構造とし、配列に対する宣言的な操作でプログラミングを行うスタイルである配列プログラミングについて説明しています。APLやNumPyなど、配列言語と配列ライブラリの概要と、配列処理の特徴が解説されています。

  1. 配列プログラミングでは、明示的な反復処理を行わない、高レベルの宣言的操作により配列にアクセスする。

  2. 配列演算は高度な数学的抽象化により、低レベルの実装の詳細を隠し、画像処理演算プロセッサ(GPU)でサポートされるような高度な並列実装に適しています。

重要な点/学んだこと

  1. 配列を主要なデータ構造とするプログラミングパラダイムである配列プログラミングを学びました。

  2. 配列プログラミングでは、ループではなく配列演算を用いてプログラミングを行います。

  3. 配列プログラミングは、数値計算や画像処理などデータ中心の並列処理に適しています。

サンプル

import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

c = a + b # 配列の演算 
print(c) # [5 7 9]

第Ⅱ部 基本スタイル 
4章 一枚岩:モノリス

プログラムをひとかたまりのコードで記述するモノリススタイルについて説明しています。goto文の多用、大域変数の使用、手続き的抽象化の不使用など、スパゲティコードを生み出しがちな特徴が紹介されています。

  1. モノリススタイルでは、ライブラリの使用を控え、手続き的抽象化も行わず、ひとかたまりの連続的なコードでプログラム全体を記述する。

  2. モノリスプログラムは保守性が低く、可読性も悪いため、ほとんどの場合このスタイルは推奨されない。

重要な点/学んだこと

  1. モノリススタイルは、プログラム全体をひとかたまりのコードで記述するスタイルであることを学びました。

  2. このスタイルでは抽象化が乏しく、保守性と可読性が低いため、なるべく避けるべきです。

  3. ただし、性能最適化などの目的で意図的に使用する場合もある。

サンプル

import random

# データ生成
data = [random.randint(0, 99) for _ in range(10)]

# データ処理
data.sort()

# 出力生成
output = ', '.join(map(str, data))

# 結果表示
print(output)


第Ⅱ部 基本スタイル 
5章 クックブック:構造化プログラミング

プログラムを手続きの集まりとしてモジュール化する構造化プログラミングのスタイルについて説明しています。手続きの定義、大域変数の共有、副作用の問題点などが紹介されています。

  1. 構造化プログラミングでは、大域変数を共有する手続きにプログラムを分割する。手続きは副作用を持つ場合がある。

  2. ダイクストラらによって提唱されたこのスタイルは、スパゲティコードを避けるための重要な発展でした。

重要な点/学んだこと

  • プログラムを手続きの集まりとして分割する構造化プログラミングの考え方を学びました。

  • 手続き間での大域変数の共有と副作用の問題に注意が必要です。

  • 構造化プログラミングはプログラムのモジュール化を推進した重要な発展です。

サンプル

# 関数の定義と呼び出し

def process_data(data):
  return [d + 1 for d in data]

data = [1, 2, 3]
processed_data = process_data(data) 
print(processed_data) # [2, 3, 4]

第Ⅱ部 基本スタイル 
6章 パイプライン:関数型プログラミング

関数をパイプラインのように連鎖させる関数型プログラミングのスタイルについて説明しています。純粋関数、副作用の回避、関数合成など、関数型言語の特徴が紹介されています。

  1. 関数型プログラミングでは、純粋関数のパイプラインによりプログラムを構成する。副作用はなくべき等性が保たれる。

  2. Lisp、Haskell、MLなどの関数型言語がこのスタイルの代表例です。

重要な点/学んだこと

  1. 関数をパイプラインのように連鎖させる関数型プログラミングのスタイルを学びました。

  2. 関数型プログラミングでは副作用を避け、べき等性を重視します。

  3. 関数型言語はこのスタイルに適していますが、命令型言語でも実現可能です。

サンプル

# 純粋関数、高階関数、map/filter/reduce

def add1(x):
  return x + 1

print(list(map(add1, [1, 2, 3]))) # [2, 3, 4] 

def is_even(x):
  return x % 2 == 0
  
print(list(filter(is_even, [1, 2, 3, 4]))) # [2, 4]

from functools import reduce
print(reduce(lambda x, y: x + y, [1, 2, 3, 4])) # 10


第Ⅱ部 基本スタイル 
7章 コードゴルフ:ワンライナー

プログラムを可能な限り最小のコード行数で記述するワンライナースタイルについて説明しています。正規表現など言語機能の活用、可読性の低下など、このスタイルの特徴が紹介されています。

  1. ワンライナースタイルでは、言語機能をフルに活用し、できるだけ少ない行数でプログラムを記述することを目指す。

  2. 過剰な短縮化は可読性や保守性を犠牲にするため、このスタイルは制限付きで使用する必要がある。

重要な点/学んだこと

  1. プログラムを最小限の行数で記述するワンライナースタイルについて学びました。

  2. 過剰な短縮化は可読性を犠牲にするので、適度な使用が必要です。

  3. 言語機能の活用は上級者の訓練に適しています。

サンプル

# 1行で記述するテクニック(リスト内包表記等)

print([x**2 for x in range(10) if x % 2 == 0]) # [0, 4, 16, 36, 64]

第Ⅲ部 関数合成 
8章 合わせ鏡:再帰

数学的帰納法に基づいて再帰を用いるプログラミングスタイルについて説明しています。基本ケースと段階的拡張の定義、適した問題、実装上の注意点が紹介されています。

  1. 再帰スタイルでは、問題を数学的帰納法に基づき、基本ケースと段階的拡張の定義で表現する。

  2. 再帰呼び出しはスタックオーバーフローに注意が必要である。

重要な点/学んだこと

  1. 数学的帰納法に基づいて再帰を利用するプログラミングスタイルを学びました。

  2. 基本ケースと段階的拡張の定義が再帰的アプローチの鍵です。

  3. 再帰はスタックオーバーフローに繋がるため、呼び出し回数に注意が必要です。

サンプル

# 再帰関数の定義

def factorial(n):
  if n == 0:
    return 1
  else:
    return n * factorial(n-1)
    
print(factorial(5)) # 120

第Ⅲ部 関数合成 
9章 継続:継続渡し

関数に継続(次に呼び出すべき関数)を渡す継続渡しスタイルについて説明しています。SchemeやLogic Programming言語での利用、非同期処理への適用などが紹介されています。

  1. 継続渡しスタイルでは、各関数は次に呼び出す関数を引数として受け取る。

  2. 制御の流れは逐次的でなくなる。非同期処理に適している。

重要な点/学んだこと

  1. 関数に次に呼び出す関数を引数として渡す継続渡しスタイルについて学びました。

  2. 制御の流れが逐次的でなくなるため、注意が必要です。

  3. 非同期処理などに利用される有用なテクニックです。

サンプル

# 継続を関数に渡す

def do_something():
    # ここでは例として、ランダムな数を返す関数とします。
    import random
    return random.randint(1, 10)

def continuable_func(k):
    result = do_something()
    return k(result)

def continuation(result):
    print(result)

continuable_func(continuation)  

第Ⅲ部 関数合成 
10章 単子:モナド

値と関数を関連付ける単子(モナド)を用いるスタイルについて説明しています。Haskellのモナド、bind操作、純粋関数型プログラミングへの適用などが紹介されています。

  1. モナドスタイルでは、値をラップし、bind操作で関数を連鎖して適用する。

  2. Haskellなどの関数型言語で副作用などを表現するのに用いられる。

重要な点/学んだこと

  1. モナドを用いて値と関数を関連付けるプログラミングスタイルを学びました。

  2. Haskellなどの関数型言語で副作用を取り入れるために利用されます。

  3. bind操作によって関数を連鎖適用できるのが特徴です。

サンプル

# モナド的な値のラッピング

class Box:
  def __init__(self, value):
    self.value = value
    
  def bind(self, func):
    return func(self.value)
  
  def __repr__(self):
    return f"Box({self.value})"

print(Box(1).bind(lambda x: Box(x+1))) # Box(2)

第Ⅳ部 オブジェクトとオブジェクトの相互作用 
11章 モノのプログラム:オブジェクト

データと手続きをカプセル化するオブジェクトを用いるオブジェクト指向のスタイルについて説明しています。クラス/オブジェクト、メソッド、継承などの概念が紹介されています。

  1. オブジェクト指向スタイルでは、データと手続きをオブジェクトにカプセル化する。

  2. メッセージパッシングによりオブジェクト間で通信する。

重要な点/学んだこと

  1. データと手続きをカプセル化するオブジェクトを用いるオブジェクト指向スタイルを学びました。

  2. メソッドによるデータの隠蔽、メッセージパッシングなどが特徴です。

  3. 継承による再利用性も特徴の1つです。

サンプル

# クラスの定義

class Cat:
  def __init__(self, name):
    self.name = name
    
  def meow(self):
    print(f"{self.name} says: Meow!")

# クラスのインスタンス化とメソッドの呼び出し    
cat = Cat("Neko")
cat.meow() # "Neko says: Meow!"

第Ⅳ部 オブジェクトとオブジェクトの相互作用 
12章 レターボックス:メッセージパッシング

オブジェクト間のメッセージパッシングによる通信を特徴とするスタイルについて説明しています。Smalltalkの影響、メッセージディスパッチなどが紹介されています。

  1. メッセージパッシングスタイルでは、オブジェクトはメッセージを受信し、内部のメソッドをディスパッチする。

  2. オブジェクトは直接メソッドを呼び出さず、メッセージで通信する。

重要な点/学んだこと

  1. オブジェクト間のメッセージパッシングによる通信スタイルを学びました。

  2. Smalltalkなどの影響を強く受けたスタイルです。

  3. メッセージのディスパッチがこのスタイルの核となる概念です。

サンプル

# メソッド呼び出しで擬似的に実現

class Cat:
    def meow(self):
        return "Neko says: Meow!"

# インスタンスを生成
cat = Cat()

cat.meow() # "Neko says: Meow!"

第Ⅳ部 オブジェクトとオブジェクトの相互作用 
13章 閉写像:プロトタイプ

プロトタイプベースのオブジェクトを用いるスタイルについて説明しています。クラスレスであること、オブジェクトのコピーによるインスタンス化などの特徴が紹介されています。

  1. プロトタイプスタイルでは、クラスを持たないプロトタイプオブジェクトを利用する。

  2. オブジェクトは辞書として表現され、コピーによりインスタンス化する。

重要な点/学んだこと

  1. プロトタイプベースのオブジェクトを用いるスタイルを学びました。

  2. SelfやJavaScriptなどの影響を受けたクラスレスなスタイルです。

  3. コピーによるインスタンス化が特徴的です。

サンプル

# プロトタイプ(Pythonではclassを使用)

class Cat:
  def __init__(self, name):
    self.name = name


# クラスのインスタンス化 
cat1 = Cat("Neko")
cat2 = Cat("Mike") # cat1をコピーせずインスタンス化

cat1.meow() # "Neko says: Meow!"
cat2.meow() # "Mike says: Meow!"

第Ⅳ部 オブジェクトとオブジェクトの相互作用 
14章 抽象的なモノ:抽象データ型

抽象データ型を定義し、その操作のみを公開するスタイルについて説明しています。インタフェース、具象クラス、静的型付けとの関係が紹介されています。

  1. 抽象データ型スタイルでは、データ型の抽象的な操作のみを公開し、具体的な実装は隠蔽する。

  2. インタフェースにより抽象的な操作を定義し、具象クラスで実装する。

重要な点/学んだこと

  1. 抽象データ型の操作のみを公開するスタイルを学びました。

  2. インタフェースと具象クラスによって実現されます。

  3. 静的型付け言語との親和性が高いスタイルです。

サンプル

# 抽象基底クラスによるインタフェース定義

from abc import ABC, abstractmethod

class Stream(ABC):

  @abstractmethod
  def read(self):
    pass

  @abstractmethod  
  def write(self, data):  
    pass

class FileStream(Stream):

  def read(self):
    print("Read from file")

  def write(self, data):
    print(f"Write {data} to file") 

# インスタンス化とメソッドの呼び出し
stream = FileStream()
stream.read() # "Read from file"
stream.write("some data") # "Write some data to file"

第Ⅳ部 オブジェクトとオブジェクトの相互作用 
15章 ハリウッド:制御の反転

コールバックによって制御の流れを逆転させるスタイルについて説明しています。イベント駆動型プログラミングとの関係が紹介されています。

  1. 制御の反転スタイルでは、オブジェクトが直接呼び出されるのではなく、コールバックに登録する。

  2. 制御の流れは反転するため、注意が必要です。

重要な点/学んだこと

  1. コールバックによって制御の流れを反転させるスタイルを学びました。

  2. イベント駆動プログラミングとの親和性が高いスタイルです。

  3. 制御の流れに注意を要するスタイルです。

サンプル

# コールバックの定義と登録

class MyObject:
  def __init__(self):
    self.callbacks = []

  def registerCallback(self, callback):
    self.callbacks.append(callback)

  def triggerCallbacks(self):
    for callback in self.callbacks:
      callback()

def callback():
  print("called")

# インスタンス化とメソッドの呼び出し  
obj = MyObject()
obj.registerCallback(callback) 
obj.triggerCallbacks() # "called"が出力される

第Ⅳ部 オブジェクトとオブジェクトの相互作用 
16章 掲示板:pub/sub

発行-購読モデルのpub/subスタイルについて説明しています。イベントの発行と購読、デザインパターンとの関係が紹介されています。

  1. pub/subスタイルでは、オブジェクトがイベントを発行し、購読者はそれを購読する。

  2. 発行者と購読者は疎結合である。

重要な点/学んだこと:

  1. 発行-購読モデルのpub/subスタイルを学びました。

  2. 発行者と購読者は疎結合であるため、柔軟なシステム設計が可能です。

  3. イベント駆動システムに適したスタイルです。

サンプル

# 発行-購読モデル(Pythonでは特に対応する構文なし)

class Publisher:
  def __init__(self):
    self.subscribers = []

  def register(self, sub):
    self.subscribers.append(sub)

  def notify(self, message):
    for sub in self.subscribers:
      sub.notify(message)

class Subscriber:
  def notify(self, message):
    print(f"Received: {message}")

# インスタンス化とメソッドの呼び出し   
pub = Publisher()
sub1 = Subscriber() 
sub2 = Subscriber()

pub.register(sub1)
pub.register(sub2)

pub.notify("Hello, world!") # sub1とsub2にmessageが通知される

第Ⅴ部 リフレクションとメタプログラミング 
17章 内省性:イントロスペクション

プログラム実行中にプログラムそのものを検査・操作するイントロスペクションについて説明しています。リフレクションとの違い、動的言語での利用が紹介されています。

  1. イントロスペクションスタイルでは、実行時にプログラム自体の情報を取得し、プログラムを変更する。

  2. 動的言語ではメタプログラミングに用いられる。

重要な点/学んだこと

  1. プログラムの実行中にプログラムそのものを参照するイントロスペクションについて学びました。

  2. 動的言語のメタプログラミングで利用されます。

  3. リフレクションとは異なり、プログラム自体は変更しません。

サンプル

# イントロスペクション

print(dir(obj)) # オブジェクトのメソッド/属性を確認
print(type(obj)) # オブジェクトの型を確認 

第Ⅴ部 リフレクションとメタプログラミング 
18章 自己反映性:リフレクション

プログラムが自身のコードを検査および変更できるリフレクションについて説明しています。Lispのメタサークル、動的言語での応用が紹介されています。

  1. リフレクションスタイルでは、プログラムは自身の定義を変更できる。

  2. Lispのメタサークルはこのスタイルの古典的な例です

重要な点/学んだこと

  1. プログラムが自身の定義を変更できるリフレクションについて学びました。

  2. Lispのメタサークルが代表例です。

  3. 動的言語ではメタプログラミングに用いられます。

サンプル

# リフレクション(動的にコードを生成する)

defadd(a, b):
  return a + b

# typeを使ってadd関数を動的に生成
add = type('Add', (), {'__call__': lambda self, a, b: a + b})() 
print(add(1, 2)) # 3

第Ⅴ部 リフレクションとメタプログラミング 
19章 横断的関心:アスペクト指向

基本的な機能と横断的な機能を分離するアスペクト指向プログラミングについて説明しています。AOPの概念、アドバイスの適用、動的AOPが紹介されています。

  1. アスペクト指向スタイルでは、横断的な機能をアスペクトとしてモジュール化する。

  2. アドバイスによりアスペクトを適用する。

重要な点/学んだこと

  1. 横断的な機能を分離するアスペクト指向プログラミングについて学びました。

  2. アスペクトのモジュール化とアドバイスによる適用が特徴です。

  3. ロギングや認証などに用いられます。

サンプル

# Pythonにはアスペクト指向プログラミング(AOP)を直接サポートする機能はありません。
# しかし、デコレータを使用してAOPのような効果を達成することができます。

def before_call_func(func):
  def wrapper(*args, **kwargs):
    print("before")
    result = func(*args, **kwargs)
    print("after")
    return result
  return wrapper

@before_call_func
def call_func():
  print("call")

call_func()
# 出力
# before 
# call
# after

第Ⅴ部 リフレクションとメタプログラミング 
20章 プラグイン:依存性注入

コンポーネントの依存関係を外部から解決する依存性注入技法について説明しています。IoCコンテナ、Springフレームワークとの関係が紹介されています。

  1. 依存性注入スタイルでは、オブジェクトの依存関係を外部から解決する。

  2. IoCコンテナによって実現される。

重要な点/学んだこと

  1. コンポーネントの依存関係を外部から解決する依存性注入技法を学びました。

  2. IoCコンテナによって実現されます。

  3. SpringのDIはこのスタイルの有名な実装例です。

サンプル

# 依存性の注入(Pythonでは特に対応する構文なし)

class Dependency:
  def run(self):
    return "Running Dependency"

class App:
  def __init__(self, dep):
    self.dep = dep

  def run(self):
    return self.dep.run()

# 依存性のインスタンス化と注入
dep = Dependency()   
app = App(dep) 

print(app.run()) # "Running Dependency"


第Ⅵ部 異常事態 
21章 構成主義:防御的プログラミング

例外的状況への備えを万全にする防御的プログラミングのスタイルについて説明しています。エラーチェックの徹底、コード複雑さへの影響が紹介されています。

  1. 防御的プログラミングスタイルでは、あらゆるエラーを想定し、防御的にコーディングする。

  2. コードは冗長になりがちだが、ロバストさが向上する。

重要な点/学んだこと

  1. あらゆる例外的状況を想定して防御的にコーディングするスタイルを学びました。

  2. 防御的すぎるとコードは複雑になるため、バランスが必要です。

  3. 高信頼性が要求される分野でよく使われます。

サンプル

# 入力データの検証

def process_data(data):
  if not isinstance(data, list):
    raise TypeError("Must be list")
    
  if len(data) == 0: 
    raise ValueError("Data must not be empty")
    
  # 本処理
  return [datum + 1 for datum in data]

data = [1, 2, 3]
print(process_data(data))  # [2, 3, 4]

第Ⅵ部 異常事態 
22章 癇癪持ち:契約による設計

関数の事前条件と事後条件を明示する契約によるデザインのスタイルについて説明しています。設計バイコントラクト、Eiffel言語での利用が紹介されています。

  1. 契約による設計スタイルでは、関数の事前・事後条件を契約として規定する。

  2. Eiffel言語のバイコントラクト機能が代表的な実装例。

重要な点/学んだこと

  1. 関数の事前条件と事後条件を契約として規定するスタイルを学びました。

  2. Eiffel言語のバイコントラクト機能が有名な実装例です。

  3. 仕様の厳密化に有用なスタイルです。

サンプル

# 契約による設計(Pythonでは対応する構文なし)

def func(x):
  # xは0以上を期待
  assert x >= 0, "x should be equal to or greater than 0"
  return x * 2  # 何らかの処理を行う

print(func(4))  # 8

第Ⅵ部 異常事態 
23章 受動的攻撃:例外

例外を利用するプログラミングスタイルについて説明しています。

  1. 例外スタイルでは、例外を積極的に利用する。

  2. 例外は通常の制御フローではなく、異常な状況を知らせる。

重要な点/学んだこと

  1. 例外の積極的な利用を特徴とするプログラミングスタイルについて学びました。

  2. 例外は異常事態を伝えるもので、通常の制御フローとは区別します。

  3. 例外の過剰使用は可読性を損なうので、節度が必要です。

サンプル

def dangerous_call():
  raise Exception("Danger!")

try:
  dangerous_call() 
except Exception as e:
  print(f"Error occurred: {e}")  # Error occurred: Danger!

第Ⅵ部 異常事態 
24章 意図の宣言:型注釈

静的型チェックを補強する型注釈を利用するスタイルについて説明しています。Haskell、TypeScriptなどでの利用法が紹介されています。

  1. 型注釈スタイルでは、変数の想定される型を明示的に注釈する。

  2. 静的型チェックを補強し、意図を明確化する。

重要な点/学んだこと

  1. 型注釈によって静的型チェックを補強するスタイルを学びました。

  2. 型システムの柔軟な言語で利用されます。

  3. 意図の表明とドキュメントの補強に有用です。

サンプル

from typing import List 

numbers: List[int] = [1, 2, 3]
print(numbers)  # [1, 2, 3]

第Ⅵ部 異常事態 
25章 検疫:純粋関数と不純関数

純粋関数と不純関数を分離するスタイルについて説明しています。副作用の局所化、テスト容易性の向上が紹介されています。

  1. 純粋関数スタイルでは、純粋関数と不純関数のコードを明確に分離する。

  2. 副作用の局所化とテスト容易性の改善を図る。

重要な点/学んだこと

  1. 純粋関数と不純関数を分離するスタイルを学びました。

  2. 副作用を純粋関数から切り離し局所化できます。

  3. テストが容易になるというメリットがあります。

サンプル

# 純粋関数

def pure_func(x):
  return x + 1

print(pure_func(2))  # 3

# 不純関数  
y = 0
def impure_func(x):
  global y
  y = x + 1

impure_func(2)
print(y)  # 3

第Ⅶ部 データ中心 
26章 データベース:SQL

レーショナルデータベースとSQLを利用するスタイルについて説明しています。正規化、ACID特性、高レベルの抽象化が紹介されています。

  1. SQLスタイルでは、リレーショナルデータベースとSQLによる高レベルのデータ操作を行う。

  2. 複雑な低レベルのデータ処理を避けることができる。

重要な点/学んだこと

  1. リレーショナルデータベースとSQLを活用するスタイルを学びました。

  2. 高レベルの抽象化によってプログラミングが容易になります。

  3. 大規模データ処理において必須の知識です。

サンプル

import sqlite3

conn = sqlite3.connect('example.db') 

c = conn.cursor()
c.execute("CREATE TABLE IF NOT EXISTS stocks (date text, trans text, symbol text, qty real, price real)")
c.execute("INSERT INTO stocks VALUES ('2023-07-16','BUY','RHAT',100,35.14)")
conn.commit()

c.execute("SELECT * FROM stocks")
rows = c.fetchall()

for row in rows:
    print(row)

第Ⅶ部 データ中心 
27章 スプレッドシート:リアクティブプログラミング

スプレッドシートの更新とリアクティブプログラミングの関係性について説明しています。リアクティブプログラミングの概念、Rxといったフレームワークが紹介されています。

  1. リアクティブプログラミングスタイルでは、データフローとイベントストリームで状態更新を表現する。

  2. スプレッドシートの更新はこのスタイルの典型例である。

重要な点/学んだこと

  1. リアクティブプログラミングのスタイルと概念を学びました。

  2. データフローとイベントストリームを中心とするパラダイムです。

  3. UIプログラミングなどに広く使われています。

サンプル

from rx import create

def push_five_strings(observer, scheduler):
    observer.on_next("Alpha")
    observer.on_next("Beta")
    observer.on_next("Gamma")
    observer.on_next("Delta")
    observer.on_next("Epsilon")
    observer.on_completed()

source = create(push_five_strings)

source.subscribe(lambda value: print(f"Received: {value}")) 

第Ⅶ部 データ中心 
28章 データストリーム:ジェネレータ

イテレータを拡張したジェネレータを用いるスタイルについて説明しています。yield、協調型マルチタスク、Lazy Evaluationの関係が紹介されています。

  1. ジェネレータスタイルでは、yieldにより値のストリームを生成する。

  2. 需要に応じた遅延評価が可能になる。

重要な点/学んだこと:

  1. ジェネレータとyieldを用いるスタイルを学びました。

  2. 需要に応じて値を生成する遅延評価が特徴です。

  3. メモリ効率の良いイテレータの拡張機能です。

サンプル

def generator():
  for i in range(3):
    yield i

for x in generator():
  print(x) 

第Ⅷ部 並行性 
29章 アクター:スレッド

Actorモデルに基づく並行プログラミングのスタイルについて説明しています。Erlang言語、メッセージパッシング、並行性の抽象化が紹介されています。

  1. アクターモデルスタイルでは、アクター間の非同期メッセージパッシングで並行タスクを実現する。

  2. Erlangはこのスタイルの代表的な言語実装例である。

重要な点/学んだこと

  1. Actorモデルに基づく並行プログラミングスタイルを学びました。

  2. メッセージパッシングによる通信が特徴です。

  3. 並行性を抽象化し、並行プログラミングを容易にします。

サンプル

from threading import Thread
import queue

# アクターをスレッドで表現
class Actor(Thread):
  def __init__(self, queue):
    Thread.__init__(self)
    self.queue = queue

  def run(self):
    while True:
      msg = self.queue.get()
      if msg == 'quit':
        break
      print('Got:', msg)
      
q = queue.Queue()
actor = Actor(q)
actor.start()

# Send messages
for count in range(10):
    q.put(count)
# Signal the thread to exit
q.put('quit')

第Ⅷ部 並行性 
30章 データ空間:並列処理

共有メモリ空間でのデータ並列型の並列プログラミングスタイルについて説明しています。GPU、OpenMP、vecotrizationが紹介されています。

  1. データ並列スタイルでは、共有メモリ空間のデータを並列に処理する。

  2. SIMD型アーキテクチャでの並列化に適している。

重要な点/学んだこと

  1. 共有メモリでデータを並列処理するスタイルを学びました。

  2. GPUやSIMD命令などのデータ並列型アーキテクチャで適しています。

  3. 並列プログラミングの重要なパラダイムの1つです。

サンプル

import multiprocessing

def func(data):
  return data * 2

data_list = [1, 2, 3, 4, 5]

with multiprocessing.Pool() as pool:
  result = pool.map(func, data_list) 

print(result)

第Ⅷ部 並行性 
31章 マップリデュース:MapReduce

MapReduceに基づく並列データ処理のスタイルについて説明しています。Hadoopへの影響、分散ファイルシステムの利用が紹介されています。

  1. MapReduceスタイルでは、マップとリデュースの並列実行により大規模データ処理を行う。

  2. Googleの影響でHadoopエコシステムなどに広まった。

重要な点/学んだこと

  1. MapReduceモデルに基づく並列データ処理スタイルを学びました。

  2. ビッグデータ処理に広く使われている分散処理フレームワークです。

  3. マップとリデュースの並列実行が特徴です。

サンプル

# Pythonの組み込み関数を使ったMapReduceの実装

data = [1, 2, 3] 

# マッパー 
def mapper(x):
  return x * 2

# リデューサー  
def reducer(x, y):
  return x + y

# map関数でマッピング、reduce関数でリデュース
mapped_data = map(mapper, data)
from functools import reduce
result = reduce(reducer, mapped_data)

print(result) # 出力: 12

第Ⅷ部 並行性 
32章 二重マップリデュース:Hadoop

HadoopのMapReduce実装を用いる並列データ処理スタイルについて説明しています。HDFS、YARN、エコシステムが紹介されています。

  1. Hadoop MapReduceスタイルでは、HDFS上の大規模データを並列処理する。

  2. MapReduceモデルの最も一般的な実装である。

重要な点/学んだこと

  1. HadoopのMapReduce実装を使う並列データ処理スタイルを学びました。

  2. HDFSとYARNによってMapReduceがサポートされている。

  3. ビッグデータ処理のデファクトスタンダードとなった。

サンプル

# Hadoop用のPython API (Pydoop, mrjob等)を使用
# この例ではmrjobを使用したコードの一部を示します

"""
from mrjob.job import MRJob

class MRWordFrequencyCount(MRJob):

    def mapper(self, _, line):
        yield "chars", len(line)
        yield "words", len(line.split())
        yield "lines", 1

    def reducer(self, key, values):
        yield key, sum(values)

if __name__ == '__main__':
    MRWordFrequencyCount.run()
"""
# 上記のコードはHadoop上で実行され、テキストの文字数、単語数、行数をカウントします。

第Ⅸ部 対話性 
33章 三位一体:MVC

モデル・ビュー・コントローラーに基づくUIアーキテクチャのスタイルについて説明しています。Webアプリケーション構築への影響が紹介されています。

  1. MVCスタイルでは、アプリケーションをモデル、ビュー、コントローラーの3要素に分割する。

  2. Webアプリケーション構築のデファクトスタンダード。

重要な点/学んだこと

  1. モデル・ビュー・コントローラーに基づくUIアーキテクチャスタイルを学びました。

  2. Webアプリケーション開発の標準的なアーキテクチャパターンです。

  3. 要件変更への対応がしやすいのが強みです。

サンプル

# MVCパターンの具体的実装

class Model:
    def __init__(self):
        self.data = "Hello, MVC!"

class View:
    def update(self, model):
        print(model.data)
  
class Controller:
    def __init__(self, model, view):
        self.model = model
        self.view = view

    def update_view(self):
        self.view.update(self.model) 

model = Model()
view = View()
controller = Controller(model, view)

controller.update_view()  # 出力: "Hello, MVC!"

第Ⅸ部 対話性 
34章 レストフル:ステートレス

ステートレスなRESTful APIを構築するスタイルについて説明しています。HTTPメソッド、リソース指向、JSONなどの関係が紹介されています。

  1. RESTfulスタイルでは、HTTPメソッドによりリソースに対する操作を実現する。

  2. ステートレスでスケーラブルなAPIデザインを可能にする。

重要な点/学んだこと

  1. ステートレスなRESTful APIの構築スタイルを学びました。

  2. HTTPメソッドによるリソース操作が特徴です。

  3. Web API設計の重要な様式の1つです。

サンプル

# Flaskを使用した簡単なRESTful APIの例

from flask import Flask, jsonify
app = Flask(__name__)

@app.route("/data", methods=["GET"])  
def get_data():
  return jsonify({"data": "hello"}) 

# if __name__ == "__main__":
#     app.run(debug=True)

# 上記のコードを実行すると、"http://localhost:5000/data" へのGETリクエストに対して
# {"data": "hello"} というJSONレスポンスが返されます。

第Ⅹ部 ニューラルネットワーク 
35章 浅いDense層のプログラム:ニューラルネットワーク

ニューラルネットワークをプログラミングする基本的なスタイルについて説明しています。ネットワークの構築、活性化関数、重みの手動設定が紹介されています。

  1. ニューラルネットワークのプログラミングでは、ノードとエッジのネットワークを構築し、重みと活性化関数を設定する。

  2. 機械学習の基礎となる計算モデルを理解する上で重要。

重要な点/学んだこと

  1. ニューラルネットワークを構築するためのプログラミングの基礎を学びました。

  2. ノードとエッジからなるネットワーク構造が特徴的です。

  3. 機械学習を理解するために重要な概念です。

サンプル

import numpy as np

# 入力データ
X = np.array([[1, 2, 3], [4, 5, 6]]) 

# 重みとバイアス
w = np.random.randn(3, 2)
b = np.random.randn(2)

# ネットワークの前向き計算
z = X @ w + b 

print(z) # 重みとバイアスを使って計算された出力

第Ⅹ部 ニューラルネットワーク 
36章 学習する浅いDense層:学習

ニューラルネットワークの学習アルゴリズムをプログラミングするスタイルについて説明しています。誤差逆伝播法、勾配降下法の利用が紹介されています。

  1. ニューラルネットの学習プログラミングでは、誤差逆伝播に基づき重みを更新する。

  2. 誤差を最小化するために勾配降下法が用いられる。

重要な点/学んだこと

  1. ニューラルネットワークの学習アルゴリズムのプログラミングについて学びました。

  2. 勾配降下法に基づいて誤差を最小化します。

  3. 機械学習プログラミングの重要な一角を占めます。

サンプル

import numpy as np

# 平均二乗誤差(MSE)
def mse(y_true, y_pred):
    return ((y_true - y_pred) ** 2).mean()

X = np.array([[1, 2, 3], [4, 5, 6]]) 
y = np.array([1, 2])

w = np.random.randn(3, 2)
b = np.random.randn(2)

lr = 0.01  # 学習率

# 後ろ向き伝播
def backprop(X, y, w, b):
  # 前向き計算
  z = X @ w + b  
  loss = mse(z, y)
  
  # 逆伝播
  dW = X.T @ (z - y) / X.shape[0]
  db = (z - y).mean(axis=0)

  w -= lr * dW
  b -= lr * db
  
  return w, b, loss

w, b, loss = backprop(X, y, w, b)
print(f"w: {w}, b: {b}, loss: {loss}")

第Ⅹ部 ニューラルネットワーク 
37章 蝶ネクタイ:多層ネットワーク

多層のニューラルネットワークをプログラミングするスタイルについて説明しています。ネットワークの深さ、畳み込みネットワークが紹介されています。

  1. 多層ネットのプログラミングでは、複数のDense層を積み重ねて深いネットワークを構築する。

  2. ネットワークの深さが表現力を高める。

重要な点/学んだこと

  1. 多層のニューラルネットワークをプログラミングするスタイルを学びました。

  2. ネットワークの深さが表現力を高めます。

  3. 画像認識では畳み込みネットワークが利用されます。

サンプル

class DenseLayer:
  def __init__(self, input_dim, output_dim):
    self.weights = np.random.randn(input_dim, output_dim)
    self.biases = np.random.randn(output_dim)

class ActivationLayer:
  def __init__(self, function):
    self.function = function

def relu(x):
  return np.maximum(0, x)

# ネットワークのレイヤーをリストで表現
network = [
  DenseLayer(5, 64),
  ActivationLayer(relu),
  DenseLayer(64, 32),
  ActivationLayer(relu),  
  DenseLayer(32, 10)   
]

# 例:最初の全結合(Dense)レイヤーの重みとバイアスを出力
print("Weights of the first dense layer:")
print(network[0].weights)
print("Biases of the first dense layer:")
print(network[0].biases)

第Ⅹ部 ニューラルネットワーク 
38章 ニューロモノリス:シーケンス

シーケンスデータを処理するRNNのプログラミングスタイルについて説明しています。状態の保持、LSTMの利用が紹介されています。

  1. RNNのプログラミングでは、ループ内で状態を保持しシーケンスを処理する。

  2. 時系列データやテキストデータの処理に適している。

重要な点/学んだこと

  1. シーケンスデータを処理するRNNのプログラミングスタイルを学びました。

  2. ループ内で状態を保持するのが特徴です。

  3. 時系列解析や自然言語処理に有用です。

サンプル

W = np.random.randn(5, 3)
U = np.random.randn(3, 3)

def tanh(x):
  return np.tanh(x)

# RNNをクラスで表現
class RNN:
  def __init__(self, input_dim, hidden_dim):
    self.W = np.random.randn(input_dim, hidden_dim)
    self.U = np.random.randn(hidden_dim, hidden_dim)
    self.state = np.zeros(hidden_dim)

  def step(self, x):
    h = tanh(x @ self.W + self.state @ self.U) 
    self.state = h
    return h 

rnn = RNN(5, 3)
x = np.random.randn(5)
h = rnn.step(x)
print(f"h: {h}")

第Ⅹ部 ニューラルネットワーク 
39章 スライディングウィンドウ:畳み込み

畳み込みニューラルネットワークをプログラミングするスタイルについて説明しています。フィルタのスライディング、プーリングが紹介されています。

  1. 畳み込みネットのプログラミングでは、フィルタをスライドさせて特徴を抽出する。

  2. 画像処理に効果を発揮するアーキテクチャです。

重要な点/学んだこと

  1. 畳み込みニューラルネットワークのプログラミングスタイルを学びました。

  2. フィルタのスライディングとプーリングが特徴です。

  3. 画像認識などのコンピュータビジョンで使用されます。

サンプル

import torch
import torch.nn as nn

class CNN(nn.Module):
  def __init__(self):
    super().__init__() 
    self.conv = nn.Conv2d(1, 1, 3) 
   
  def forward(self, x):
    x = self.conv(x) 
    return x

cnn = CNN()
x = torch.randn(1, 1, 28, 28)
output = cnn(x)
print(f"Output: {output}")

第Ⅹ部 ニューラルネットワーク 
40章 リカレント:RNN

LSTMなどのRNNをプログラミングするスタイルについて説明しています。内部状態、セル状態の保持が紹介されています。

  1. RNNのプログラミングでは、内部状態とセル状態を保持するループで学習する。

  2. 時系列データの長期依存性を学習できる。

重要な点/学んだこと

  1. RNNとLSTMのプログラミングスタイルを学びました。

  2. 内部状態とセル状態の保持がポイントです。

  3. 時系列データの長期依存性の学習に有用です。

サンプル

import torch
import torch.nn as nn

class RNN(nn.Module):
  def __init__(self, input_size, hidden_size):
    super().__init__()
    self.rnn = nn.LSTM(input_size, hidden_size, batch_first=True)
    
  def forward(self, x):
    x, _ = self.rnn(x)
    return x

rnn = RNN(10, 50)
x = torch.randn(1, 5, 10)  # (batch_size, sequence_length, input_size)
output = rnn(x)
print(f"Output: {output}")

おわりに

この本から、多様なプログラミングスタイルとその特徴を学ぶことができました。制約からスタイルが生まれるという本書のメッセージは、プログラミングに新たな視点をもたらしました。今後は状況に応じた適切なスタイルを選択し、信頼性と保守性の高いプログラムを書けるようになりたいと思います。

参考文献


この記事が気に入ったらサポートをしてみませんか?