もしRubyistがHaskellを学んだら(17) letとwhere
タグ: learning_haskell / 初版公開: 2014-01-11

再びHaskellでの実装を念頭に置いてRubyで実装したプログラムをHaskellで書き下すスタイルで勉強してみる。今回は円柱の体積の計算を通じて、Haskelのletwhereについて見ていく。

いささか無理のある例ではあるが、半径と高さがわかっているたくさんの円柱の体積を求める必要があるとする。Rubyで実装すると以下のプログラムが1つの答えになる。

def cylinderVolumes(cylinders)
	def volume(radius, height)
		pi = 3.14
		radius ** 2  * pi * height
	end

	cylinders.map{|cylinder|
		r = cylinder[0]
		h = cylinder[1]
		volume(r, h)
	}
end

p cylinderVolumes [[1.0, 1.0], [2.5, 1.0], [2.5, 2.5]]

Rubyにはタプルは存在しないので、2要素のリストを便宜的に円柱の半径と高さを保持した要素としよう。この要素のリストが体積を求めたい円柱の一覧である。

これをHaskellで実装する。Haskellでは局所関数や変数の定義にletwhereを用いる。今回は双方のパターンで実装することにする。

いずれの例も、円周率を示す局所変数piと、円柱の体積を計算する局所関数volumeを定義する。Haskellにはタプルが存在するので、円柱の半径と幅はタプルに格納し、計算を行うcylinderVolumes関数はそのリストを受け取るようにする。

cylinderVolumes :: [(Double, Double)] -> [Double]
cylinderVolumes xs = [volume r h | (r, h) <- xs, let pi = 3.14 ; volume radius height = radius ^ 2 * pi * height]
cylinderVolumes :: [(Double, Double)] -> [Double]
cylinderVolumes xs = [volume r h | (r, h) <- xs]
	where pi = 3.14
	      volume radius height = radius ^ 2 * pi * height

あとはmainからcylinderVolumesを呼び出してやれば、円柱の体積を求めることができる。GHCiからこれらのプログラムをロードして実行させてみよう。

*Main> cylinderVolume [(1.0, 1.0), (1.0, 2.5), (2.5, 2.5)]
[3.14,7.8500000000000005,49.0625]

さて、今回のテーマはletwhereだが、その違いはHaskell初心者にはなかなかに難しい。ポイントとしては以下のようである。

  • letは式であり、whereは式ではない
  • whereのスコープは局所的でなく関数定義のガードを跨いで使うことができる

“すごいHaskellたのしく学ぼう!”の3章には以下の記載がある。むむむむむ。結局は適材適所で使い分け?(逃げた)

はじめのうちは、letは束縛を先に書いて式を後に書くけどwhereはその逆という違いしかないように思えるかも知れません。 本当の違いは、let式はその名のとおり「式」で、where節はそうじゃない、というところです。 (中略) つまり、let式は次のようにコード中のほとんどどんな場所でも使えるということです。 ghci > 4 * (let a = 9 in a + 1) + 2 42 (中略) また、関数の前ではなく後ろで部品を定義するのが好きだからwhereを使うという人もいます。

すごいHaskellたのしく学ぼう!
Miran Lipovača
オーム社
売り上げランキング: 126,678