回答:
GHCは機能を記憶しません。
ただし、コード内の指定された式は、周囲のラムダ式が入力されるたびに1回だけ計算されます。最上位の場合は、最大で1回計算されます。例のように構文シュガーを使用する場合、ラムダ式がどこにあるかを判断するのは少しトリッキーになる可能性があるので、これらを同等の脱糖構文に変換してみましょう。
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(注:Haskellの98のレポート、実際のように左オペレータセクション記述(a %)
と同等にし\b -> (%) a b
ますが、GHCはそれをdesugars (%) a
彼らはで区別することができますので、これらは技術的に異なっている。seq
私はこのことについてGHC Tracのチケットを提出しているかもしれないと思います。)
これを考えると、あなたはその中を見ることができm1'
、式はfilter odd [1..]
それはあなただけのプログラムの実行ごとに一度計算されますのでにおけるながら、任意のλ式に含まれていないm2'
、filter odd [1..]
ラムダ式が入力されるたびに計算されます、すなわち、の呼び出しごとにm2'
。これにより、タイミングの違いがわかります。
実際、特定の最適化オプションを備えた一部のバージョンのGHCは、上記の説明が示すよりも多くの値を共有します。これは、状況によっては問題になることがあります。たとえば、関数を考えます
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHCは、関数にy
依存せずx
、関数を書き換えないことに気付く場合があります。
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
この場合、新しいバージョンはy
、格納されているメモリから約1 GBを読み取る必要があるため、効率が大幅に低下しますが、元のバージョンは一定の領域で実行され、プロセッサのキャッシュに収まります。実際、GHC 6.12.1では、関数f
は、最適化なしでコンパイルした場合、でコンパイルした場合よりも約2倍高速-O2
です。
f
:main = interact $ unlines . (show . map f . read) . lines
。コンパイルの有無にかかわらず-O2
; その後echo 1 | ./main
。のようなテストを作成するmain = print (f 5)
と、y
使用時にガベージコレクションが行われ、2つf
のに違いはありません。
map (show . f . read)
もちろんそうです。GHC 6.12.3をダウンロードしたので、GHC 6.12.1と同じ結果が表示されます。そして、はい、あなたは右の元程度あるm1
とm2
変えていく有効な最適化を持ち上げるのこの種を行うGHCのバージョン:m2
にm1
。
m1は定数適用形式であるため、1回だけ計算されますが、m2はCAFではないため、評価ごとに計算されます。
CAFのGHC wikiを参照してください:http ://www.haskell.org/haskellwiki/Constant_applicative_form
[1 ..]
がプログラムの実行中に1回だけ計算されるか、関数のアプリケーションごとに1回計算されるかですが、CAFに関連していますか?
m1
CAFあり、第二が適用され、filter odd [1..]
(だけではなく[1..]
!)一度だけ計算されます。GHCも注意してください可能性がm2
を指しfilter odd [1..]
、そして中に使用したのと同じサンクへのリンクを配置しますm1
が、それは悪い考えのようになります。それは、いくつかの状況では、大きなメモリリークにつながる可能性があります。
[1..]
とに関する修正をありがとうございますfilter odd [1..]
。残りについては、私はまだ確信が持てません。私が間違っていない場合、CAFは、コンパイラがfilter odd [1..]
in m2
をグローバルサンク(これはで使用されているものと同じサンクでさえあり得る)に置き換えることができると主張したい場合にのみ関連しm1
ます。しかし、質問者の状況では、コンパイラーはその「最適化」を行わなかったので、質問との関連性がわかりません。
m1
、そしてそれはありません。
2つの形式の間には決定的な違いがあります。単相性の制限はm1に適用されますが、m2には適用されません。したがって、m2のタイプは一般的ですが、m1のタイプは特定です。それらに割り当てられるタイプは次のとおりです。
m1 :: Int -> Integer
m2 :: (Integral a) => Int -> a
ほとんどのHaskellコンパイラーとインタープリター(私が実際に知っているものすべて)はポリモーフィック構造を記憶していないため、m2の内部リストは呼び出されるたびに再作成されますが、m1はそうではありません。
私自身はHaskellに慣れていないのでわかりませんが、2番目の関数はパラメーター化されており、最初の関数はパラメーター化されていないためです。関数の性質は、その結果は入力値に依存し、関数パラダイムでは特に入力のみに依存するということです。明白な意味は、パラメータのない関数は、何があっても常に同じ値を繰り返し返すということです。
どうやら、この事実を利用してプログラム全体のランタイムに対して一度だけそのような関数の値を計算するGHCコンパイラーには最適化メカニズムがあります。確かにそれは怠惰に行いますが、それでもそれを行います。次の関数を書いたとき、私はそれに気付きました。
primes = filter isPrime [2..]
where isPrime n = null [factor | factor <- [2..n-1], factor `divides` n]
where f `divides` n = (n `mod` f) == 0
次に、それをテストするために、GHCIに入り、次のように書きましたprimes !! 1000
。数秒かかりましたが、ようやく答えが出ました7927
。それから私は電話primes !! 1001
をして、即座に答えを得ました。同様に、すぐに私はの結果を得ました。これは、take 1000 primes
Haskellが前に1001番目の要素を返すために1000要素のリスト全体を計算する必要があったためです。
したがって、パラメータを取らないように関数を記述できる場合は、おそらくそれが必要です。;)
seq
m1 10000000)。ただし、最適化フラグが指定されていない場合は違いがあります。ちなみに、 "f"の両方のバリアントは、最適化に関係なく、最大で5356バイトの常駐を持っています(-O2が使用されている場合、総割り当ては少なくなります)。