他人のHaskell日記 RSSフィード

Haskell初心者が、リハビリがてらに「ふつける」と「入門Haskell」片手に、試行錯誤するサイト。

2008-12-23

Haskell プログラミングガイドライン Haskell プログラミングガイドライン - 他人のHaskell日記 を含むブックマーク

http://www.haskell.org/haskellwiki/Programming_guidelinesを読みながらメモ。

  • haddockが読めるように

{- |

Module : <File name or $Header$ to be replaced automatically>

Description : <Short text displayed on contents page>

Copyright : (c) <You> and <Your affiliation>

License : similar to LGPL, see LICENSE.txt

Maintainer : Christian.Maeder@dfki.de

Stability : provisional

Portability : portable

<module description starting at first column>

-}

というフォームから開始せよ

  • 「関数はshort&sweetであるべきで、一つの事しかしちゃならん。1画面か2画面で収めろ。ひとつのことをちゃんとやれ。」
  • ほとんどのhaskellの関数は少ない行数であるべき。巨大なデータ型を扱う(まあ、それ自体を避けるべきだけど)場合のcaseは例外。
  • コードは十分かつ明瞭であれ。読みやすく、予想できる変化に対して保守しやすくせよ。理由がなければ、エキゾチックな言語の使用を無駄づかいするな。
  • インデントの深さは指定されていない。
  • {や;を使うのはやめろ
  • コンパイラーの警告に耳をかたむけろ。型宣言をせよ。シャドウイングや未使用の変数は避けよ。パターンは漏れなくダブリなく書け。ありえないパターンはerror <Module Name> <Function>で埋めておけ。
  • 未使用やコメントアウトされたコードをファイルに残すな。読者が混乱する。
  • case式はたいていdeclaration Styleより望ましい。後者は引数の数や関数名が変わったら面倒だから。
  • expression Style(\ t->~)もお勧めしない。const, flip, curry, uncurry、セクション記法、部分適用を使えばたいてい必要ない。まあでもλを消すためだけに補助関数を使うぐらいならexpressin Styleもいいかもしれない。
  • 末尾の空白は全部消せ
  • タブ文字は空白に置きかえろ。
  • 1行80(できれば75)文字以内に収めるべき
  • 全体のモジュールは400行以内にすべき
  • 名前はcamelCaseで付けろ。qualifiedも使え。
  • 部分関数が前提とする条件が明らかでないのならドキュメントを書け。caseを使うなり、事前のテストをするなりして、前提条件が満さされたときのみに部分関数が呼ばれるようにしろ。特にheadを呼ぶときは気をつけろ。あるいは(もっといい手段として)caseを使うことによってheadの使用を避けろ。caseが嫌ならmaybeを使うことで避けれるぞ。とにかく失敗するパターンについては明白であれ。
  • より複雑な事をしようとしているのなら、型の同義名やタプルより適切なデータ型の方が望ましい。そうすれば、後に型クラスを提供するのも簡単になる。コンパイラーにチェックされないため、長期間一貫した型の同義名を使うのは難しい。
  • クラス束縛はデータ型に対してではなく、そのデータを扱う関数に対して行うこと。
  • リスト内包表記は"Short and Sweet"なときだけ使い、普段はmap filter foldrを使え。後々、リスト以外のデータ構造に変えるときも楽だ。
  • 中置き演算子を使えば括弧を減らせるぞ。$や.は便利だぞ。
  • $の周りには空白を残せ.templete haskellで問題がおきないし。(\t->~)と書かずに(\ t ->~)とかけ。
  • letとwhereの混同とネストを防ぐため、top-levelの補助(auxiliary)関数を使え。ただし、その補助関数をexportするな。exportリストは未使用な関数を発見するのに便利。
  • 同じタスクを何度も書いているのに気がついたら、一般化してコードの複製を防げ。バグがでたら面倒だ。
  • 巨大なレコードを扱うときは、コンストラクターを直接使うべきでない。フィールドの数や順番が変わったとき困る。
  • IO,モナド、純粋なdoを含まない関数を厳密に分割することを試せ。
  • Preludeのintaractは使うな。プログラムが(いつも明白ではない)評価順序に依存しないようにしろ。
  • traceはデバッグのためだけに使え
  • Char. List, Maybe, Monadは階層的モジュール名でimportせよ。
  • SetやMapをimportするときはqualifiedを使え。

モジュールを適切にImportしよう


http://www.haskell.org/haskellwiki/Import_modules_properly

他のモジュールから識別子をimportするときにいろいろやりかたがあるが、見た目ほど便利ではないやりかたもある。


推奨:顕示(explicit) import

import qualified Very.Special.Module as VSM
import Another.Important.Module (printf)

非推奨:匿名(anonymous) import

import Very.Special.Module
import Another.Important.Module hiding (open, close)
スタイル
前者では、プログラムを読んでいるときにprintfやVSM.openを見かけたら、その識別子がどこからやってきているのかわかる。後者では、識別子がVery.Special.ModuleからきたのかAnothoer.Important.Moduleからきたのか、それとも他のモジュールから来ているのか区別できない。grepしても無駄だ。Very.Special.ModuleやAnother.Important.Moduleは他のモジュールを再exportしているかもしれないからだ。
複雑性
後者では、importしているモジュールに新たに識別子が加わったら他のモジュールと名前が衝突するかもしれない。だからimportされているをモジュールをアップデートすれば、あなたのコードを破壊する可能性がある。 Another.Important.Module.openを隠したということは、それを軽視している事を意味するが、モジュールがアップデートするときに、この識別子を取り除いたとき、あなたのimportは失敗する。この識別子はまったく必要ではないのに、貴方を何度も困らせることになる。前者では、このような問題は起きない。
正確性
わたしはかつて、匿名importから顕示importに変更したときに、StorableVectorパッケージのバグを見つけたことがある。このモジュールの関数はnot "byte"ではなくunit "element"を計算しなければならないにも関わらず、Foreign.Ptr.plusPtrがimportされていると分った。これは advancePtrが代わりに使われたに違いない。実際にreverse関数はplusPtrを使っており、これは間違っていた。ある無作法は、1バイトよりも大きなsub-vectorsとelementsを扱ったときにのみ観察される。テストスイートはそれを見逃していた。

:例外:Preludeはこれからも不変であることを意図していいるから、Preludeをimportするときにhidingを使うのは問題ない。しかも何も書かなけりゃ自動的にimportされてしまうわけだから。


単純から複雑に

http://www.haskell.org/haskellwiki/Simple_to_complex

一般に、単純な関数を組みあわせて複雑な関数を作るのはいいアイデアだ。

複雑な関数のスペシャルケースとして単純な関数を作るような戦略は、それ単体では上手くいかない。そのような複雑な関数はそもそも、その関数自身に、いろいろなものを組みあわせていかねれば作れない。これでは関数依存のグラフがもつれてしまい、関数は明確な階層にはならない。

Haskell遅延評価機能があるので、単純な関数から複雑な関数を作るのだという原則を忘れてしまいがちだ。数学者がHaskellを遣う11の理由を見てほしい。そこでは「3次元ベクトルの外積を結果の単一成分が必要とされたなら、二つの3次元ベクトルの外積の計算を自動的に簡潔にしてくれる」という遅延評価の利点が紹介されている。

しかしながら、外積の単一成分を計算することは、2x2行列の行列式を計算するのと同じ意味であり、それ自身が有用なものである。

だから、よりよいコンセプトは、外積を行列に簡約する遅延を使わないで、2x2行列の行列式を計算する明確な関数を記述し、外積の計算の中ではこれを3度呼び出すというものである。


他の悪例は数字の線形代数パッケージMatLabだ。その型階層構造は複素数行列から始まっており、ユーザはそこから更に複雑な型を作れる。つまり、より単純な型は存在せず、実数行列もなければ、複素数、実数、整数、ブールも無い。それらは、複素数行列で表現されなければならない。これはあまり自然ではない。なぜなら、transcendent powers のようないくつかの操作は、行列には簡単には移植できないからだ。たくさんの操作において、入力値が例えば1x1行列として適切な特性を持っているかどうかを実行時にチェックされなければならない。実際に、整数や論理値といった、いくつかの種類が後に追加されたが、それはMatLabの基本となる型と奇妙な相互作用をする。

MatLabの言語デザイナーがやった誤りは次のようなものである。

彼等はMatLabが永遠に線形代数用の特別な目的を持った言語でありつづけると考え、この領域で出会うもっとも複雑な型のみを実装することに決めた。MatLabが成長するにつれて、プログラムを新たな需要(画像のインポート、エクスポート、GUIプログラミング等)に合わせるためにプログラムを拡張したが、ユニバーサルな型についての最初の決定は上手くスケールしなかった。

これをHaskellで真似るのは名案ではない。単純な型を作ることから始め、それを使って複雑なものを作ろう。ライブラリの中核に手の込んだ型を使ってはいけない。もしそうだとしても、それらを可能な限り階層の下に(葉のモジュールに)追いやろう。

型クラスメソッド

Data.Listにある多くのメソッド(map, filter, foldr, (++)等)は、他のデータ構造にも一般化できそうです。なぜ一般化されたもの(すなわち型クラスのメソッド)に置き変えられていないのか?と不思議に思います。

  • まず、説明的な理由がある。 foldrのような、高階関数は初心者には理解しがたいものだ。だから彼等は再起を書くことに執着しがちになる。

Data.List.foldlが隠され、その一般化であるData.Foldable.foldl(要素の型や働く関数が一般化されただけにとどまらず、ユーザが利用するような特定のデータ構造も存在しない)のみが使えるようになったとしよう。そこには、もはや具体的なものは存在しない。あるのは抽象だけである。抽象的な関数を一番良く説明するのは例を使ううことだ。例えば sum xs = List.foldl (+) 0 xs……いまや、List.foldlが必要となってしまった。

  • それと近い理由として、コードの分かり易さの理由がある。mapやfilterを見ればリストが使われていると分かる。プログラムの読者が人間型推論を始める必要はない。関数がJustやリストやライトなどをモナドに応じて返しうるとして、それを使ってIO(Maybe a)を返すようなコードを書いたとしたら、プログラムの読者は、帰り値がIOなのかMaybeなのか推論しなければならず、特定の型を処理したいのならば、それを読者に伝えなければならない。
  • 最後でかつ重大な理由に、型安全とコンパイラとのコミュニケーションがある。もしコンパイラが、特定の型のためにプログラミングしているとコンパイラが知っていれば、より強力に型を検査できるし、より正確なエラーメッセージが出せる

Data.List.filterを一般化してみよう。

filter :: (MonadPlus m) => (a -> Bool) -> m a -> m a
filter p m = m >>= \a -> if p a then return a else mzero

これは良いものだが、Data.List.filterを取り替えるべきでない。 一般すぎるコードを書くとコンパイラの型推論は失敗する、GHCiで上の定義のfilterを持っているときに

Prelude> filter Char.isUpper (return 'a')

と書いたとする。これは、どんなモナドだと特定されるべきだろうか?Maybe?IO?リスト?デフォルトの型に頼るべきか?結局、型注釈を付けるはめになるだろう。

これらは、標準ライブラリのみに起こる問題ではなく、カスタム型クラスを設計するときも考慮に入れられなければならない。Slim instance declarations.を見よ

Haskell programming tips Haskell programming tips - 他人のHaskell日記 を含むブックマーク

http://www.haskell.org/haskellwiki/Haskell_programming_tips

序文

このページではコードが改善されていく例を見ていきます。そこから一般的な法則を会得しようと努めましょう。とはいっても何にでも適用できるわけじゃありませんし、趣味の問題もあるかもしれません。私達はそれを知っていますから「これは議論の対象だ」と全ての項目に付け加えないでください。(代わりに/Discussionになら「これは議論の対象だ」と加えても構いません。そこで一定のコンセンサスが得られたなら、このページの中身が変わります。)


簡潔であれ。

車輪を再発明しないように。

標準ライブラリは、役に立つ機能でいっぱいで、時には多すぎるぐらいです。あなたが既存の機能を書き直すなら、読者は、それと標準の関数との違いは何なのかと惑います。しかし、あなたが標準関数を使用するなら、読者は新しくて、何か役に立つものを学ぶかもしれません。 適切なリストの関数を見つけるのにお困りでしたら、このガイドを用いてください。

http://www.cs.chalmers.se/Cs/Grundutb/Kurser/d1pt/d1pta/ListDoc/


明示的な再帰を避けます。

明示的な再帰は一般には悪くはありませんが、高階関数を使った、より宣言的な実装を見つけるべきでしょう。

次のような定義はやめましょう。

raise :: Num a => a -> [a] -> [a]
raise _ [] = []
raise x (y:ys) = x+y : raise x ys

「どれだけのリストが処理されて、出力されるリストの要素がどの値に依存しているのか」を読者が把握するのが難しいからです。ただ、次のように書きましょう。

raise x ys = map (x+) ys

更に次のように書きましょう。

raise x = map (x+)

こうすれば、「全リストが処理されて、出力される要素の各々が、入力されたリストの対応する要素だけに依存する」ことが読者に解ります。

適切な関数を標準ライブラリで見つけることができなかったなら、一般的な関数を抽出しましょう。そうすれば、あなたや他の人がプログラムを理解しやすくなります。Haskellはコードの部分を取り出すのが、とても得意です。それがとても一般的だと分かったなら、それをモジュールに分類して再利用しましょう。それはいずれ標準ライブラリに登場するか、あるいは……既に存在することにあなたがいつか気が付くでしょう。

(訳注:この辺りはd:id:y_fukayaさんが訳してくれました。コメント欄をご覧ください。感謝します!)

このような方法で問題を分解すると、デバッグが楽になるという利点もあります。raise の後者の実装が意図したとおりに動かないときは、map の実装(これは合っていると思いたいですが :-) )を調べることと、(+) に渡されたインスタンスを調べることを、別々に行えます。

もっと一般化できるでしょうか。 raise を map と (+) に分解するこの例は、物事を分割する際の原則の、一つの具体例 (a special case) であるように思えます。原則とは、つまり次の二つに分割することです: コレクション全体に対する繰り返しの処理 (ここでは map) と、コレクションの各要素に適用する処理 (ここでは (x+)) 。あるデータ構造 (リスト、ツリー、その他なんでも) の全体に対する繰り返し処理は、一度書いてしまえば、何度もコピーする必要はありません (少なくとも Haskell では) 。これによりあなたのコードは OnceAndOnlyOnce の原則に従っていられます。この原則は、ある程度以上関数型プログラミングをサポートする言語でないと守るのがとても困難なものです (i.e. Java ではコピー&ペースト式のプログラミングが必要になりますし、C# の delegate の構文は機能しますが不恰好です――ほとんど 金メッキでしょう)





別の例:関数countは特定の特性を満たす要素(つまり述語pがTrueな要素)の数を数えます。

私は次のようなHaskellのコードを発見しました。(より特定の関数に巻きこまれていましたが)

count :: (a -> Bool) -> [a] -> Int
count _ [] = 0
count p (x:xs)
  | p x = 1 + count p xs
  | otherwise = count p xs

このような書き方は、次の書き方に気がついていたら好きにはなれないでしょう。

count p = length . filter p

必要な識別子だけを導入しましょう。

このアドバイスは全ての言語について有効であり、科学的な処理においても重要です(http://www.cs.utexas.edu/users/EWD/transcriptions/EWD09xx/EWD993.html)。 必要な識別子だけを導入してください。GHCに-Wallのようなオプションを渡せば、コンパイラがチェックしてくれることでしょう。

[a | i <- [1..m]]

aは恐ろしく複雑な式であり、iに全く依存していないのだ、という事を理解するのは大変です。

replicate m a

の方が明らかにいいですよね。


ゼロを忘れないで。

ゼロが自然数であったのを忘れないでください。 (訳注:ゼロが自然数かどうかは流儀によるようです)再帰のアンカー(訳注:再帰の終端。これが無いと無限ループに)が適切に選ばれていないと、再帰的関数は非常に複雑になります。「Haskell:離散数学のための良いツール」という本で紹介されている関数tuplesはガードを避けるための良い例です。(訳注:ここでのタプルは、タプル型とは別。一般に「順序を持った組」の事をタプルと言う。)



tuples :: Int -> [a] -> [[a]]
tuples r l
  | r == 1 = [[el] | el <- l]
  | length l == r = [l]
  | otherwise = (map ([head l] ++) (tuples (r-1) (tail l)))
  ++ tuples r (tail l)

何をしているか分かりますか?

(訳注:実行例を載せときます

*Main> tuples 3 [1,2,3,4]
[[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
*Main> tuples 2 [1,2,3,4]
[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

)


ガードを取り除いて、リスト内包表記を忘れましょう。

tuples :: Int -> [a] -> [[a]]
tuples 1 l = map (:[]) l
tuples r l =
  if r == length l
    then [l]
    else
      let t = tail l
      in map (head l :) (tuples (r-1) t)
      ++ tuples r t

tuplesの引数にゼロが与えられたら、どんな振舞いをするでしょうか?そのパターンを加えてみましょう。

tuples 0 _ = [[]]

もはやtuples 1のパターンは必要ないので消せます。

tuples :: Int -> [a] -> [[a]]
tuples 0 _ = [[]]
tuples r l =
  if r == length l
  then [l]
  else
    let t = tail l
    in map (head l :) (tuples (r-1) t)
                   ++ tuples r t

r > length lの場合はどうでしょうか。headが失敗するのを放置しておく理由は明らかにありません。与えられたリストよりも長いタプルは作れません。だから空リストを返すことにしましょう。これは特別な状況で私達を救ってくれます。

tuples :: Int -> [a] -> [[a]]
tuples 0 _ = [[]]
tuples r l =
  if r > length l
  then []
  else
    let t = tail l
    in map (head l :) (tuples (r-1) t)
                   ++ tuples r t

「length is evil」だと学びました。次のようにしてみたらどうでしょう。

tuples :: Int -> [a] -> [[a]]
tuples 0 _ = [[]]
tuples _ [] = []
tuples r (x:xs) =
  map (x :) (tuples (r-1) xs)
  ++ tuples r xs

lの長さを何度も何度も計算する必要はなくなりました。コードは読みやすくなり、全ての特別な場合をカバーするようになりました。tuples (-1) [1,2,3]のような場合も含めてです!

このlengthチェックの消去はある状況(例:tuples 24 [1..25])において性能を劇的に悪化させます。length l < rの代わりにnull (drop (r-1) l)を使うこともできます。これは無限リストにも対応しています。下の例を見ましょう。

tailsが提供する全てのsuffixのリストを明示的に計算することによって、一方向の再帰を保つこともできます。

do記法を使ってこうも書けます。

tuples :: Int -> [a] -> [[a]]
tuples 0 _ = [[]]
tuples r xs = do
  y:ys <- tails xs
  map (y:) (tuples (r-1) ys)

リストモナドにおける(=<<)はconcatMapですから次のようにも書けます。

前のバージョンではパターンマッチy:ysが最後の空のsuffixを取り除いていましたが、今や手動でやらなければならなくなりました。そこでinitを使います。

tuples :: Int -> [a] -> [[a]]
tuples 0 _ = [[]]
tuples r xs =
  concatMap (\(y:ys) -> map (y:) (tuples (r-1) ys))
            (init (tails xs))

全てのsuffixのリストはiterate tailでも生成できますが、これはPrelude.tail: empty list".というエラーを発生させて終了します。tailsが生成するものは同じものですが適切に終了します。

より一般的には「Base cases and identities」をご覧ください。

λを使いすぎないようにしましょう。

明示的な再帰と同様に明示的なλは、必ずしも悪いものではありませんが、たいていはもっといい解決策が存在するものです。例をあげましょう。

Haskellカリー化が凄く得意なので、次のように書くのは止めましょう。

zipWith (\x y -> f x y)

map (\x -> x + 42)

代わりに、こう書きましょう。

zipWith f

map (+42)

また、このように書く代わりに

-- 文字列のリストを大文字小文字を区別せずにソートする
sortBy (\x y -> compare (map toLower x) (map toLower y))

こう書きましょう。

comparing p x y = compare (p x) (p y)

sortBy (comparing (map toLower))

これなら明確かつ再利用可能です。実はGHC-6.6以降ではcomparingを書く必要がありません。Data.Ordモジュール(http://www.haskell.org/ghc/dist/current/docs/libraries/base/Data-Ord.html)の中に既に存在していますから。


この特別な例についての簡単な注意:変換を複数回評価することを避けることができます。

sortKey :: (Ord b) => (a -> b) -> [a] -> [a]
sortKey f x = map snd (sortBy (comparing fst) (zip (map f x) x))

(訳注:いわゆるシュワルツ変換ですね。http://www.wdic.org/w/TECH/%E3%82%B7%E3%83%A5%E3%83%AF%E3%83%AB%E3%83%84%E5%A4%89%E6%8F%9B)

経験則として、式が長くなりすぎて簡単にポイントフリーに出きそうにない場合は、とにかく名前を付けた方がいいでしょう。しかしながら、たまにはλが適切な場合もあります。例えば、モナディックなコードの中におけるコントロール構造などです。(次の例におけるコントロール構造foreach2は、殆どの言語ではそもそもサポートすらしていないでしょう。)

foreach2 xs ys f = zipWithM_ f xs ys

linify :: [String] -> IO ()
linify lines
        =foreach2 [1..] lines $ \lineNr line -> do
           unless (null line) $
               putStrLn $ shows lineNr $ showString ": " $ show line
Boolは普通の型

論理表現はガードやif分の中に制限された存在ではありません。こういうくどさは避けましょう。

isEven n
 | mod n 2 == 0 = True
 | otherwise = False

これは、次と一緒です。

isEven n = mod n 2 == 0

シンタックスシュガー(構文糖衣)を賢く使いましょう。

シンタックスシュガーを使えば読みやすくなると主張する人々がいます。以下のセクションでは、シンタックスシュガーを減らした方が読みやすくなる例を示します。「特別な記法は純粋な関数による表現よりも、しばしば直感的である」という主張は良く聞きます。 しかしながら「直感的な記法」というものは、いつだって習慣の問題なのです。最初に見たときは馴染みがないような分析的表現に対する直感は発達させることができるものです。そうなると、時折シュガー(砂糖)を減らそうという習慣が身に付きます。

リスト内包表記

リスト内包表記は利用者を命令的な考え方に留めます。変換よりも変数について考えさせるからです。頭を解き放ちポイントフリーの趣を発見しましょう。

[toUpper c | c <- s]

と書く代わりに

map toUpper s

こう書きましょう

次の例を考えてみましょう。

[toUpper c | s <- strings, c <- s]

値がいったい何に依存しているのか理解するのに時間がかります。sやcが何度使われるのか明白ではありません。次と比べましょう

map toUpper (concat strings)

これより明白なものはありませんね。

高階関数を使っていれば、リストから他のデータ構造へと簡単にスイッチできます。

次の二つを比較しましょう

map (1+) list
mapSet (1+) set

Functorクラスの標準インスタンスが存在すれば、次のコードを両方の場合に使えます。

fmap (1+) pool

高階関数について知らなければ、並列リスト内包表記が欲しくなるでしょう。これは不幸な事ですが今やGHCではサポートされています。これは間違いなく過剰です。既に、たくさんのzip風のそれが、素晴しい仕事をしているからです。


do 記法

do記法は命令的な性質(隠れた状態や、実行の順番など)のコード片を表現するのに便利です。しかしながら、do記法が関数を使って表現できる事を覚えていると役に立つことがあります。

do
  text <- readFile "foo"
  writeFile "bar" text

と書く代わりに、こう書けます。

readFile "foo" >>= writeFile "bar"
do
  text <- readFile "foo"
  return text

というコードは「Monadが満たさねばならない法則」のおかげで次のように書けます。

readFile "foo"

あなたは、次のコードが

do
  text <- readFile "foobar"
  return (lines text)

次のコードより複雑であることにも同意するでしょう。

liftM lines (readFile "foobar")

ところでFunctorクラスのfmapメソッドとMonad-basedな関数であるliftMは同じものです。(両方とも正しく定義されていればですが。)

(訳注:Monadは論理的にFunctorなのにも関わらず、FunctorがMonadのスーパークラスではないことを問題視し、クラス階層を変更すべきだという提案がなされています。 http://www.haskell.org/haskellwiki/Functor_hierarchy_proposal をご覧ください。)


「より複雑だ」ということは「悪い」という事を意味しません。これよりもdo記法が長くなるようならば、do記法とfmapを混ぜるべきではないでしょう。「自然であれ」という事を考えるようにしましょう。変化させるのは、変化から何か得られるときのみにしましょう。


ガード

免責:このセクションはガードを使うなとアドバイスしているのではありません。両方とも使用可能ならばパターンマッチングの方がガードよりも好ましいと言っているのです。

-- ダメな実装
fac :: Integer -> Integer
fac n | n == 0 = 1
      | n /= 0 = n * fac (n-1)

これは階乗を求める関数です。この例のようにガードをたくさん使うと、問題がたくさん発生します。

最初の問題は、ガード条件が恣意的に複雑なため(GHCは-Wallオプションを使っていれば警告します。)コンパイラがこのようなガードに漏れがないのかチェックするのはほとんど不可能だと言うことです。

その問題と、漏れのあるパターンがもたらす潜在的なバグを回避するためにotherwiseガードを使えます。

-- いくらか改善された実装
fac :: Integer -> Integer
fac n | n == 0    = 1
      | otherwise = n * fac (n-1)

これが好ましいのには別の理由もあります。人間にとって可読性が高いという点と、コンパイラが最適化しやすいという点です。

このような単純なケースではあまり問題になりませんが、もっと複雑な場合でも、otherwiseを見れば、それが使われるのは他のガードが失敗したときだけだと、ただちに明らかになります。 同じ事がコンパイラにも言えます。Trueの同義語であるotherwiseを最適化するのは可能ですが、他の殆どの式に対しては期待できません。

-- シュガー減量  (if-then-elseの多弁さは、シュガーと捉えることができますが……)
fac :: Integer -> Integer
fac n = if n == 0
          then 1
          else n * fac (n-1)

ifには別の問題群があります。レイアウトルールに関するものであったり、ifのネストは読みにくいというものだったりします。(ifのネストを避けたいならCaseの項目を読んでください)

この特別な例では、パターンマッチングを使うことで、より簡単にできます。

-- 良い実装
fac :: Integer -> Integer
fac 0 = 1
fac n = n * fac (n-1)

実際には、このケースでは、もっと読みやすいバージョンがあります。それは明示的な再帰を使わない方法です。

-- 素晴らしい実装
fac :: Integer -> Integer
fac n = product [1..n]

これは効率的でもあります。productはライブラリの作者によって最適化されていると思われます。GHCで最適化を有効にして実行すると、このバージョンが使用するスタックスペースはO(1)になります。それと比べて、前のバージョンはO(n)のスタックスペースを使用します。

このバージョンと前のバージョンには違いがあります。負の数が与えられると前のバージョンは終わりませんが、このバージョンは1を返します。

いつだってガードがコードを綺麗にするとは限りません。次のコードを比較してみましょう。

foo xs | not (null xs) = bar (head xs)
foo (x:_) = bar x

あるいは次の二つの例を比較しましょう。(パターンガードの例)

parseCmd ln
   | Left err <- parse cmd "Commands" ln
     = BadCmd $ unwords $ lines $ show err
   | Right x <- parse cmd "Commands" ln
     = x
parseCmd ln = case parse cmd "Commands" ln of
   Left err -> BadCmd $ unwords $ lines $ show err
   Right x  -> x

読者がeither関数と親しみ深ければ、次のコードもいいでしょう。

parseCmd :: -- add an explicit type signature, as this is now a pattern binding
parseCmd = either (BadCmd . unwords . lines . show) id . parse cmd "Commands"

ついでに言えば、コンパイラーはしばしば数値パターンについての問題を抱えています。たとえばパターン0は、実際は「fromInteger 0」です。関数のパラメータのパターンとして一般的できない計算が呼ばれることを意味します。これを説明するために、次の例について考えてみましょう。

(訳注:Haskell98の次の標準規格であるHaskell Primeの議論において、数値パターンを排除すべきでないか、という提案がなされています。「みんな使っているし、無いと冗長になる」という反論もなされています→http://hackage.haskell.org/trac/haskell-prime/wiki/RemovalCandidates )


data Foo = Foo deriving (Eq, Show)
 
instance Num Foo where
    fromInteger = error "forget it"
 
f       :: Foo -> Bool
f 42    = True
f _     = False

 *Main> f 42
 *** Exception: forget it

ガードは必要なときのみ使いましょう。可能なときはいつでもパターンマッチングに頼りましょう。

n+k パターン

数値型へのパターンマッチを認めるためにHaskell 98はいわゆる n+k patternsを提供しています。

take :: Int -> [a] -> [a]
take (n+1) (x:xs) = x: take n xs
take _     _      = []

しかしながら、これらは計算の複雑性を隠し、曖昧さを導入していると、しばしば批判されています。詳しくは/Discussionを読んでください。

このパターンは、より一般的なViews提案(残念ながら、長い時間がたっているのにもかかわらず実装されたことはありませんが)に包括されます。

(訳注:n+kパターンはHaskell Primeでは排除される候補のひとつです。http://hackage.haskell.org/trac/haskell-prime/wiki/RemovalCandidates)

効率と無限

経験則「無限のデータ構造に対しても意味が通るはずの関数が、無限のデータに対して適用することで失敗するようであれば、その関数は恐らく有限のデータに対しても適用したときも効率が悪い。」

必要も無いのにリストの長さを尋ねるのは止めましょう。

リストxが空かを調べるのに次のように書くのは止めましょう。

length x == 0

このように書くとHaskellはリスト全てのノードを作成せねばならず、無限リストの場合Falseが帰ってくるべきなのに失敗します。(ちなみに、それでもリストの中身までは評価されないでしょう。)

これと比べ

x == []

は高速です。しかし、リストxの型を[a]だとすると、aはEqクラスの型でなければなりません。

ベストな手段は次の通りです。

null x

加えていうと、length関数を使用しているとき、多くの場合は、問題を十分に限定できていないのです。リストの長さが知りたいのではなく、「リストが少くともある長さであるか」をチェックしたいのなら、lengthを「リストの長さが必要な最小限の長さよりも長いか」を調べるatLeast関数で置き変えるべきです。

atLeast :: Int -> [a] -> Bool
atLeast 0 _      = True
atLeast _ []     = False
atLeast n (_:ys) = atLeast (n-1) ys

再帰を避けるために、効率が落ちてもいいのなら(takeもlengthもノードの数を数えなければならないので無駄があります)次のように書けます。

atLeast :: Int -> [a] -> Bool
atLeast n x = n == length (take n x)

再帰を避けながら、効率も落としたくないのならば、次のように書くといいでしょう。

atLeast :: Int -> [a] -> Bool
atLeast n =
  if n>0
    then not . null . drop (n-1)
    else const True

あるいは次のような書き方もできます。

atLeast :: Int -> [a] -> Bool
atLeast 0 = const True
atLeast n = not . null . drop (n-1)

「あるリストを、他のリストの長さと同じだけ短かくしたい」場合に、次のコードでは同じ問題が起きます。

take (length x) y

これは、巨大なリストに対しては非効率であり、無限リストに対しては失敗します。しかしながら、無限リストから有限のprefixを抽出することは便利かもしれません。それなら代わりにこう書きましょう。

zipWith const y x

これなら上手く動きます。

lengthはgenelicLengthに置きかえることができ、それはPeano数が利用できることを覚えておきましょう。(訳注:genelicLengthとPeano数(遅延に的した構造を持つ)の組み合わせで遅延カウントができるらしいです→http://www.haskell.org/haskellwiki/Peano_numbers)


必要もないのに最小値を尋ねるのは止めましょう。

次の関数isLowerLimitは、ある数がリストの最小値かどうかをチェックします。


isLowerLimit :: Ord a => a -> [a] -> Bool
isLowerLimit x ys = x <= minimum ys

これはysが無限ならば明らかに失敗します。これは問題だといえるでしょうか?

次と比較しましょう.

isLowerLimit x = all (x<=)

この定義ならxが最小値でなければ無限リストを与えても終了します。

xより小さな値が見付かればただちに終了します。つまり有限のリストに対しても速いということです。更に空リストに対しても動きます。


共有しましょう。

もし、中身は同じで長さが伸びていくリストを作りたいのなら、次のようには書かないでください。

map (flip replicate x) [0..]

なぜなら、2乗の空間と実行時間を必要とするからです

iterate (x:) []

こうすれば、リストはsuffixを共有するので、作成するのに線形の空間と実行時間しか必要としません。


(訳注:「tailsもリストのコピーを作らなくて効率的」という話も知っておくと便利なケースがあるかもしれません。 http://haskell.g.hatena.ne.jp/jmk/20060913)


適切なfoldを選びましょう。

どちらのfoldが状況に適切なのかというアドバイスが「

[x !! i - i | i <- [0..n]]
zipWith (-) x [0..n]
効率性ヒントの項目で述べられている通りです。


型クラス制約を減らそう

delete, (\\), nub等の関数を使うにはEQクラスの型が必要になることを覚えておきましょう。リストに複数の等しい要素が含まれていたら期待したようには動いてくれないという事と、関数のように要素の型が比較できないなら使えないという事です。

例)次の関数は入力されたリストxsから、xsの要素を一つづつ取り除いた、それぞれのものを返します。

何をいってるか解りますか?分かりませんか?恐らくコードならもっと理解しやすいと思います。

removeEach :: (Eq a) => [a] -> [[a]]
removeEach xs = map (flip List.delete xs) xs

これは次のように書き換えるべきです。

removeEach :: [a] -> [[a]]
removeEach xs =
   zipWith (++) (List.inits xs) (tail (List.tails xs))

これならaが関数型であっても、xsに等しい要素があっても完璧に動きます。


  • 整数を考慮していないならIntを使うのを止めましょう。

Cのスタイルのように、あらゆるものに整数を使う前に、もっと特定の型を使う事を考えましょう。

0と1にしか興味がないのならBoolを使いましょう。事前に定義された選択肢があり、数値的な演算子が必要ないのなら、列挙型を試しましょう。

次のように書かず

type Weekday = Int

このように書きましょう。

data Weekday = Monday
             | Tuesday
             | Wednesday
             | Thursday
             | Friday
             | Saturday
             | Sunday
  deriving (Eq, Ord, Enum)

これは ==, <, succのような目的に敵った演算子は許容し、+, *のような意味のない演算子を禁止します。

こうすれば、間違って曜日と数字を混ぜたりできませんし、weekdayをパラメータに取る関数のシグニチャを見れば、期待するデータの種類が何なのかを明白に分かります。

列挙型が不適切な場合は、必要としている型に最も近い型を転用してnewtypeを定義できます。

たとえば、オブジェクトを一意的な識別子と関連付けたいとします。

整数型を選びたくなるかもしれませんが、演算が必要ないのなら、次のように実際の整数と区別した型を作れます。

newtype Identifier = Identifier Int deriving Eq

その他

IOとデータ処理を分離しましょう

IO Monadをどこでも使うのはよくありません。 ほとんどのデータ処理はIOとの相互作用無しに書けます。 データ処理とIOを分けて、(実行順序を特定する必要がなく、何が実際に必要な計算なのか考えなくてすむように)純粋な関数的に処理できるようにしましょう。純粋関数的にデータを処理し、それを簡潔なIOとの相互作用で出力すれば、遅延評価の便益を受けられます。

-- import Control.Monad (replicateM_)
replicateM_ 10 (putStr "foo")

は明らかに次のコードよりも悪いです。

putStr (concat $ replicate 10 "foo"

同様に、

do
  h <- openFile "foo" WriteMode
  replicateM_ 10 (hPutStr h "bar")
  hClose h

は次のように短かくできます。

writeFile "foo" (concat $ replicate 10 "bar")

これは、失敗したときはファイルハンドルhが適切に閉じられるようになっています。

カスタムした分布を持つランダムな値を計算したい関数(distInvは分布関数を反転させます)はIOを使って

次のように定義できます。

randomDist :: (Random a, Num a) => (a -> a) -> IO a
randomDist distInv = liftM distInv (randomRIO (0,1))

しかし、こうする必要はありません。乱数生成器の状態を覚えるためだけに、全ての世界の状態を記憶しておく必要は無いからです。そこで、次のようにしたらどうでしょう。

randomDist :: (RandomGen g, Random a, Num a) => (a -> a) -> State g a
randomDist distInv = liftM distInv (State (randomR (0,1)))

Stateを走らせることで実際の値を得られます。次のようにしましょう。

evalState (randomDist distInv) (mkStdGen an_arbitrary_seed)
quotとremについては忘れましょう。

こやつらは負の被除数を扱うのを難しくさせます。

divとmodは、たいていいつも良い選択であり、b > 0なら次を満たします

a == b * div a b + mod a b
mod a b < b
mod a b >= 0

quotとremでも最初の等式はTrueですが,他の二つはmodにはTrueなのにremにはFalseです。

つまりmod a b は[0..(b-1)]の間にaをラップするものですが、rem a b の符号はaの符号に依存しているのです。

これは「優秀な理由」というよりも経験の問題のようです。なので被除数の符号が除数の符号よりも重要な場合もあると反論するかもしれません。しかしながら、私はそのような適所を見かけたことはありません。quotとremが使われていたとしても、多くの場合はdivやmodを使った方が良いのです。

  • 連続的に数えられたトーンピッチを音程C(ド),D(レ),E(ミ)のクラスに変換したい場合: mod p 12
  • 要素の数がmの倍数になるようにpaddingする場合: xs ++ replicate (mod (- length xs) m) pad
  • 日付データを曜日に換算する場合: mod n 7
  • パックマンが画面から走り出て画面の逆から現われる場合: mod x screenWidth

次の文章も読んでください。


fromJustやheadのような部分関数

fromJustやheadのように、特定の値が入力されると失敗するような関数は避けましょう。実行時にしか検知できないエラーを起こします。プログラムを別のやりかたで組み上げることによって部分関数の使用を回避できないか考えるか、もっとエラーを起こさないような限定された型を選びましょう。

次のように書かず

if i == Nothing then deflt else fromJust i

このように書きましょう。

fromMaybe deflt i

(==)はEQクラスインスタンスをiの型として要求しますがfromMaybeはパターンマッチングを行うので、それを必要しない事に気付いてください。


このようにfromJustを避けることができない場合でも、とにかくfromMaybeを使って「あなたの状況では何故値が常にJustになると考えているのか」をerrorに付記してください。

fromMaybe (error "Function bla: The list does always contains the searched value")
          (lookup key dict)

「決して空にならないような型」を使うことでheadの問題を避けることが出来ますが(訳注:CycloneやCwという言語では、そうしているようです。) 、Maybeを使ったやりかたもあります。

関連するリンク

Haskell初心者がやりがちな一般的な間違いや間違った信念 Beginners
いくらか一般的な、それほど一般的でないHugs Errors

y_fukayay_fukaya2008/12/24 18:05はじめまして。
意味がとれないとおっしゃっている部分があったので、翻訳の参考になればと訳してみました。原文を盛大に破壊しています (間に勝手に文を補ったりしてます) が、ニュアンスとしてはこういうことではないかと思います。


Decomposing a problem this way also has the advantage that you can debug more easily. If the last implementation of raise does not show the expected behaviour, you can inspect map (I hope it is correct :-) ) and the invoked instance of (+) separately.

このような方法で問題を分解すると、デバッグが楽になるという利点もあります。raise の後者の実装が意図したとおりに動かないときは、map の実装(これは合っていると思いたいですが :-) )を調べることと、(+) に渡されたインスタンスを調べることを、別々に行えます。

Could this be stated more generally? It seems to me this is a special case of the general principle of separating concerns: iteration over a collection vs operating on elements of a collection should apply. If you can write the loop over a data structure (list, tree, whatever) once and debug it, then you don't need to duplicate that code over and over (at least in haskell), so your code can follow the principle of Wiki:OnceAndOnlyOnce ; Wiki:OnceAndOnlyOnce is a lot harder in languages that don't provide a certain level of functional programming support (i.e. Java requires copy and paste programming, the delegate C# syntax is clumsy but workable - using it is almost Wiki:GoldPlating).

もっと一般化できるでしょうか。raise を map と (+) に分解するこの例は、物事を分割する際の原則の、一つの具体例 (a special case) であるように思えます。原則とは、つまり次の二つに分割することです: コレクション全体に対する繰り返しの処理 (ここでは map) と、コレクションの各要素に適用する処理 (ここでは (x+)) 。あるデータ構造 (リスト、ツリー、その他なんでも) の全体に対する繰り返し処理は、一度書いてしまえば、何度もコピーする必要はありません (少なくとも Haskell では) 。これによりあなたのコードは Wiki:OnceAndOnlyOnce の原則に従っていられます。この原則は、ある程度以上関数型プログラミングをサポートする言語でないと守るのがとても困難なものです (i.e. Java ではコピー&ペースト式のプログラミングが必要になりますし、C# の delegate の構文は機能しますが不恰好です――ほとんど Wiki:GoldPlating でしょう)


文中 Wiki:OnceAndOnlyOnce や Wiki:GoldPlating と書かれているのは、推測ですが Wikipedia への interwiki ではないかと思います。英語版 Wikipedia の OnceAndOnlyOnce の項は "Don't repeat yourself" の項にリダイレクトされていて、これには日本語版の記事 (http://ja.wikipedia.org/wiki/Don't_repeat_yourself) がありますから、これにリンクするのが無難かなあと思います。

GoldPlating は自分もいまいち意味がとれていません。単純に、関数型的な機能は C# という言語に後付けされた(金をメッキ処理された)ものだということかと推測するのですが、何か他の意味合いで使っているのかもしれません。

以上、お役に立てば幸いです。

taninswtaninsw2008/12/24 19:27ご協力ありがとうございます。反映……というかコピペさせていただきました。

http://en.wikipedia.org/wiki/Gold_plating_%28disambiguation%29
ここの項目を見る限りでは「望まれたものを越えた価値の無い無駄な機能追加」という意味なのかなぁ、と思いました。

ThitaThita2013/03/29 16:50At last, smooene who comes to the heart of it all

fgfiinfgfiin2013/03/31 22:16oHw9Ul , [url=http://xxqubjmimmgh.com/]xxqubjmimmgh[/url], [link=http://tsulqdptgaqj.com/]tsulqdptgaqj[/link], http://sdafaupukvii.com/

bnbkfxdfabnbkfxdfa2013/04/01 05:09yRGC6C <a href="http://lyuyazfamgba.com/">lyuyazfamgba</a>

uhyfaumoeguhyfaumoeg2014/04/12 20:58vmiyjibtlfmm, <a href="http://www.ftgcmaoiso.com/">mgofkntkfh</a> , [url=http://www.grpdcysrgi.com/]pylrgcmetb[/url], http://www.uoojtwewed.com/ mgofkntkfh

uqawoporaxofuqawoporaxof2017/03/27 02:57http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

ixubviqomupuixubviqomupu2017/03/27 03:01http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

ilauniwoqibehilauniwoqibeh2017/03/27 03:09http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

utoweliutoweli2017/03/27 03:15http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

idibinarizoidibinarizo2017/03/27 03:20http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

ijizifisuodiijizifisuodi2017/03/27 03:29http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

eqaupimelxoeqaupimelxo2017/03/27 03:34http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

oduyudaixuoduyudaixu2017/03/27 03:53http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

ezetaciezetaci2017/03/27 03:58http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

upuguwatbaupuguwatba2017/03/27 04:11http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

esihibepiqpicesihibepiqpic2017/03/27 04:18http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

azafiqiniuuazafiqiniuu2017/03/27 04:37http://usa-onlineprednisone.net/ - usa-onlineprednisone.net.ankor <a href="http://salbutamol-ventolin-buy.net/">salbutamol-ventolin-buy.net.ankor</a> http://online-viagracanada.net/

oepohehajuuheoepohehajuuhe2017/05/21 06:45http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

oqinavuvezeqioqinavuvezeqi2017/05/21 06:58http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

axoneujitorfaxoneujitorf2017/05/21 07:08http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

aigoxeiyifoaigoxeiyifo2017/05/21 07:10http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

iksewumoiksewumo2017/05/21 07:13http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

ejurijuxuejurijuxu2017/05/21 07:20http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

orjozovocoborjozovocob2017/05/21 07:30http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

ejepukavubozaejepukavuboza2017/05/21 07:33http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

ukezosoxalaukezosoxala2017/05/21 07:57http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

onominwinonominwin2017/05/21 08:11http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

edijuhizeyoedijuhizeyo2017/05/21 08:11http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

equsisqoluequsisqolu2017/05/21 08:23http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

aquxowibephibaquxowibephib2017/05/22 07:19http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

aqelovoaqelovo2017/05/22 07:33http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

okuremusulobiokuremusulobi2017/05/22 17:15http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

eueroyuzieueroyuzi2017/05/23 07:23http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

ufeboziyerogufeboziyerog2017/05/23 19:17http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

apuexibaloapuexibalo2017/05/23 19:30http://100mgcheapest-price-viagra.com/ - 100mgcheapest-price-viagra.com.ankor <a href="http://tadalafil-buy-5mg.com/">tadalafil-buy-5mg.com.ankor</a> http://20mgprednisone-order.com/

ecaduqivaecaduqiva2017/08/11 08:25http://20mg-cheapesttadalafil.com/ - 20mg-cheapesttadalafil.com.ankor <a href="http://20mg-cheapesttadalafil.com/">20mg-cheapesttadalafil.com.ankor</a> http://20mg-cheapesttadalafil.com/

opuhokazopuhokaz2017/08/11 08:38http://20mg-cheapesttadalafil.com/ - 20mg-cheapesttadalafil.com.ankor <a href="http://20mg-cheapesttadalafil.com/">20mg-cheapesttadalafil.com.ankor</a> http://20mg-cheapesttadalafil.com/

acuquhoacuquho2017/08/11 08:38http://20mg-cheapesttadalafil.com/ - 20mg-cheapesttadalafil.com.ankor <a href="http://20mg-cheapesttadalafil.com/">20mg-cheapesttadalafil.com.ankor</a> http://20mg-cheapesttadalafil.com/

galopacirgalopacir2017/08/11 08:46http://20mg-cheapesttadalafil.com/ - 20mg-cheapesttadalafil.com.ankor <a href="http://20mg-cheapesttadalafil.com/">20mg-cheapesttadalafil.com.ankor</a> http://20mg-cheapesttadalafil.com/

ejohatabakejohatabak2017/08/11 08:51http://20mg-cheapesttadalafil.com/ - 20mg-cheapesttadalafil.com.ankor <a href="http://20mg-cheapesttadalafil.com/">20mg-cheapesttadalafil.com.ankor</a> http://20mg-cheapesttadalafil.com/

afohqiqunaceafohqiqunace2017/08/11 08:59http://20mg-cheapesttadalafil.com/ - 20mg-cheapesttadalafil.com.ankor <a href="http://20mg-cheapesttadalafil.com/">20mg-cheapesttadalafil.com.ankor</a> http://20mg-cheapesttadalafil.com/