なぜ初心者にHaskellのファンクターは怖いと言われるのか(翻訳)
英語の勉強のためにWhy say the design of functor in Haskell is terrible for NEWBIE | neutronestを翻訳してみました。
以下翻訳
ファンクターって何?
今日私が話したい"ファンクター(functor)"は圏論(category theory)の概念ではなく、Haskellの中心的機能と言語の特性です。実はHaskellのファンクターのデザインは数学のコンセプトを起源にしています。最初にファンクターの定義の詳細を見なおしてみましょう。
class Functor f where fmap :: (a -> b) -> f a -> f b
このコードで、(a -> b)
という型シグネチャはa
という型の値を適用するとb
という型の値が返ってくる関数を意味します。ここでf
は関手の構造(functorial structure)(気をつけて!このf
は(a->b)
のような関数ではありません)です。従って、f a
は引数a
をf
に与えたという意味ではありませんが、型a
のものをf
という構造に入れたことを表します。
f
が困惑させる
今のところHaskell初心者には全く問題ないでしょう。私達マヌケな人間(そう、もちろん私も含めて)はHaskellの新しい重要な機能を理解することに大喜びします。続けてインスタンス実装です。例えば、リストは
instance Functor [] where fmap f [] = [] fmap f (x:xs) = (f x) ++ (fmap f xs)
ええと、待って、待って。ここのf
はどういう意味?他の関手の構造?もちろん違います。なぜなら[]
はファンクターの定義と対応しているf
なのでf
は(a -> b)
であるという型情報を私達は知っているからです。
多分誰かがこの違いはそんなに重要ではないのでファンクターの実装の型シグネチャをチェックするときだけ気をつければよいと言うかもしれません。OK、それらを一緒に混ぜた私達Haskell初心者をとても混乱させる例を見せましょう。
Prelude> let rmp = const 'p' Prelude> :t rmp rmp :: b -> Char Prelude> let lms = [Just "123", Nothing, Just "abc"] Prelude> :t lms lms :: [Maybe [Char]]
何も問題ないね?いくよ。
Prelude > fmap rmp lms "ppp"
簡単です。Chatのリスト(訳注:Charのタイポ?)を作るためにlms
のそれぞれの要素を'p'
に置き換えただけです。従って"ppp"
になります。
もっと複雑なもの
Prelude> (fmap . fmap) rmp lms [Just 'p',Nothing,Just 'p'] Prelude> :t (fmap . fmap) rmp lms (fmap . fmap) rmp lms :: [Maybe Char] Prelude> :t (fmap. fmap) (fmap. fmap) :: (Functor f, Functor f1) => (a -> b) -> f (f1 a) -> f (f1 b)
ここでは何が起きた?なぜfmap
を加えるとMaybe
型が出てくるのでしょう?(fmap . fmap)
のf (f1 a)
は何を意味しているのでしょうか?入れ子になった二重の関数呼び出し?実はコンパイラはf
とf1
が両方ともファンクターであることを知っています。しかし多くの人、Haskell初心者はf (f1 a)
を2つの関数呼び出しと見做すかもしれません。まずf1 a
を計算し、その値を最終的な結果を得るためにf
に適用するというふうに。
実際、この例は最新のHaskellの本(この本は最新のGHCのたくさんの機能と変更をカバーしていてとても良いです)から来ています。正直に言うと私はマヌケなので自分でファンクターの定義をもう一度書くまでの数日間ここで行き詰まりました。f (f1 a)
は私を困惑させ、入れ子になった関数ではなく入れ子の構造を意味していると理解するまでに時間がかかりました。
ファンクター?関数?
私はファンクターと関数が圏論で高いレベルで抽象化されて同じものになっているかどうか知りません。みんなが圏論で武装した研究者ではないのです。もしHaskell研究者が「ファンクターはほにゃららな構造で〜」と2つは別物であると決めても、f
は別物であるとしない方が良いです。
ファンクターへの個人的な変更
私がこの例にしばらく詰まったあと、本の関連するセクションを読み直してもっと簡単に理解するためにファンクターを書き直しました。
class Structor s where smap :: (a -> b) -> s a -> s b instance Structor [] where smap f [] = [] smap f (x:xs) = (f x)++(fmap f xs)
例を再検査して型情報を見る。
Prelude> (smap . smap) rmp lms [Just 'p',Nothing,Just 'p'] Prelude> :t (smap . smap) rmp lms (smap . smap) rmp lms :: [Maybe Char] Prelude> :t (smap. smap) (smap. smap) :: (Functor s, Functor s1) => (a -> b) -> s (s1 a) -> s (s1 b)
Functor
をStructor
に、fmap
をsmap
に、f
をs
(sはstructureの意味)に変えた以外何もしていません。これでs
とf
に惑わされることはありません。その上、s (s1 a)
を見た時にすぐに入れ子の構造であると分かります。
まとめ
今回私が話した問題はちっぽけです(ただ名前を選んだだけ)。数学の概念とそれに関連するHaskellの機能を持ち込むことは時には不必要です。初心者が理解することの障害になります。熟練したHaskellプログラマははっきりと分かっているのでこの問題を放置するかもしれません。モナドでも同様です。知識はないけどやる気のある人に困難を作らないでください。
翻訳ここまで
途中で「二重の関数呼び出しじゃなくて二重の構造」って言ってるのに、直後で「その2つを別物と思わないほうが良い」って書いてあるような気がするんですがどういうことでしょう。
精巣捻転になりました
こんにちはtechno_tanoCです。
学校の定期試験がひとまず終わって、追試に向けて勉強をしなくてはと思いながらも怠惰な性格なもので勉強せずに部屋でゴロゴロしていたのですが、精巣捻転になりました。治療を受けたところすっかり良くなって、今のところ痛みは全くないです。
事の発端は今日の13時頃、トイレに行ったあともうちょっとゴロゴロしようと布団に潜り込んだ時に腹部に違和感。最初は「お腹の調子が悪いのかなー」ぐらいに考えていたのですが、いくら経っても全く治まる気配がありません。20分ほど痛みに悶ていたのですが、そこでふと気づきました。「そういえばこの痛み、金的を食らった時と似ている」。男性であれば一度は体験したことのあるあの痛みです。睾丸からお腹に響いてくるあの重くも鋭い痛みです。私の場合、睾丸そのものよりもお腹の痛みが大きく気づくのが遅くなりましたが確かに睾丸の方も痛く、睾丸が動くと痛みも激しくなる。陰嚢に触ってみると右の睾丸が腫れていました。まずすることはもちろんググることです。「睾丸 腫れ」と検索をかけたところ、「精巣腫瘍(睾丸腫瘍)」「精巣炎」「 精巣捻転」「6~8時間以内に処置しないと壊死する」などの怖そうな文章がズラズラ出てきてやっぱりヤバそうだと確信しました。その時は精巣捻転だと思っていたわけではないのですが、「もし精巣捻転だったら早く処置しないとヤバい」という情報を見て放置してはいけないと思いました。
痛みで若干朦朧としながら、症状と一緒に調べておいた最寄りの泌尿器科に行きます。この時が大体14時だったので発症から1時間ですかね。徒歩1分の場所に泌尿器科の診療所があってラッキーでした。これなら痛みを我慢して歩いていけそうです。
が、診療所の前まで行ってみると入り口が閉ざされており、よく見ると今日の診療時間は15時からだそうです。痛いお腹を抱えてグルグル考えた結果、「別の泌尿器科で最も近いものは電車でひと駅、そこまで辿り着く自信がない。もう一時間待ってから来よう」となり、とぼとぼ家に帰りました。
家に帰っても痛みは全く引くことはなく、布団のなかで悶え続けていました。金的の痛みがずっと続くことを想像して頂ければ良いと思います。すごく辛いです。今朝見たスライムを引き伸ばしたり引き千切ったりする夢を思い出しながら「俺が何をしたっていうんだ・・・!」とか考えていました。この一時間は本当に長かったです。
そんな感じでなんとか1時間痛みに耐え、再度診療所に向かいました。若干ふらついていたと思います。
診療所に入り、問診票を受け取ったのですが、歩いたせいか症状が悪化し冷や汗が止まらなく意識も遠のいて問診票を書くことができませんでした。それを見た診療所の方にすぐに診察室に運び込まれ、処置を受けました。
診察台に寝かされ、医師が来るのを待っている最中、離れたところから看護師の方の「捻転?」という声が聞こえて怖かったです。横になったら少し楽になって冷や汗も引きました。その時に測った血圧は92/70、体温は36.2℃だったと思います。目眩で目がよく見えなかったのですが、手も蒼白になっているらしい。
本来受付が15時で治療は15時半からだったのですが、急を要すると判断されたのかすぐに医師の先生がいらっしゃいました。やはり精巣捻転だったらしく、処置が始まりました。ネットで精巣捻転の項目を見た時に「切開して精索の捻れを戻す」とか書いてあったので、「あぁ手術するのか・・・」と覚悟していましたが、実際に行われた処置は違いました。
簡単に言うと「直接玉を引っ張って捻じれを直す」。ただでさえ痛い睾丸を引っ張られました。ものすごく痛かったです。変な声出ました。なんだこのダイナミックチンポジ直し。
そんな感じに数分間引っ張られた結果、医師の先生曰く「腫れも引いた」とのこと。腫れは捻転したせいで血流が途絶えたのが原因だったらしいです。そしてあの痛みはなんだったのかというほど痛みはなくなり、数分後には看護師の方に貰った蜂蜜飴を舐めながら横になっていました。あと最後にエコーを撮られました。
しばらく様子を見て、もう大丈夫そうだと判断されたので処方箋を貰って退院(?)。動いても痛みはなく、妙に感動しました。処方箋に書かれたロキソプロフェンナトリウムの文字を見て「これ学校でやったやつだ!」とか思う余裕があるほど無痛です。薬局で薬を貰い、「また発症したらどうしよう」と若干不安になりながら帰宅。今に至ります。
体験レポートとしてはこんな感じでしょうか。
まとめというほどではないですが、身体に痛いところがないって素晴らしいですね。みなさんも身体に不調があればすぐにお近くの医療機関に行くことをお勧めします。私の場合、あのまま放置していたらと思うと恐ろしいです。
WordPressで作られたサイトにログインする
やりたいことはタイトルそのまま。haskellで書く。
使うパッケージはhttp-contuid 2.1.8とutf8-string 1.0.1.1(stackのlts-3.14)
{-# LANGUAGE OverloadedStrings #-} import qualified Data.ByteString.Lazy.UTF8 as U import Network.HTTP.Conduit ap = [("account", "name"), ("pwd", "password")] main = do man <- newManager tlsManagerSettings log' <- parseUrl "http://piyopiyo/wp-login.php" let log= urlEncodedBody ap log' cookies <- httpLbs log man >>= return . responseCookieJar req' <- parseUrl "http://piyopiyo/target.html" let req = req' { cookieJar = Just cookies } httpLbs req man >>= return . U.toString . responseBody >>= putStrLn
main
の前半でセッション情報を含んだCookieを取得して、後半でそれを使ってログインしていないと見れないページを取得。
requestBody
にログイン情報を乗せて失敗してハマった。urlEncodedBody
を使うのがポイント。
「関数型」というジャンル
多くのプログラミング言語で関数型プログラミング要素が取り込まれるようになって久しいです。
- Java8でラムダ式が導入された時は大きな話題になりました。
- C#では2.0でジェネリクスが使えるようになり、3.0ではラムダ式と型推論(のようなもの)も使えるようになりました。
- ClojureやScalaでは基本的にイミュータブルな値を使うコーディングが推奨され、関数合成も使います。時には遅延評価も使います。
- Rubyのブロックは実質、高階関数ですね。
- Shemeでは末尾再帰最適化が言語仕様として要求されます。
一部、関数型と言うにはやや強引なものもありますがラムダ式、ジェネリクス、型推論、イミュータブル、関数合成、遅延評価、高階関数、末尾再帰最適化、モナドなど関数型プログラミングには多くの側面があります。
今回は「関数型とは何か」のような話はしません。関数型というある意味バズワードとなってしまった言葉への一つの考え方を話します。
突然ですが私はEDMやハードコアミュージックが好きです。techno-tanoCというハンドルネームもHARDCORE TANO*Cが元ネタです。ハードコアテクノと一口に言ってもその分類はたくさんあり、wikiには現在12種類の分類が載っていますがこれもハードコアテクノの分類の一部に過ぎません。MainStreamGabbaやBrostepなどのようにここ数年生まれたような音楽ジャンルもあります。ジャンルの移り変わりの速さや新しいジャンルの発生の多様性はIT業界と同じかそれ以上と思います。
そんな音楽の世界でジャンルとは何なのでしょうか?BPS(beat per second)や使う音の傾向で分類されることが多いですが、これから外れる曲も多いです。一番無難な分類法は作曲者による宣言でしょう。youtubeやニコニコ動画に上げられる多くの曲にはタイトルにジャンルが書かれていることが多く、「この曲は作曲者がこのジャンルだと言っているからこのジャンルだ」と考えることです。もちろん聞く人によって「この曲はこのジャンルの方が適切ではないか」ということはあると思いますし、私も時々「この曲は自分の思うジャンルの傾向と違う」と思うことはありますがそれを批難する人は原理主義者ぐらいでしょう。
閑話休題、「関数型」というのもこれに似たものではないでしょうか? 先ほど挙げた特徴を以って言語を「関数型」と分類することは簡単です。しかし「関数型」には明確な定義がありません。従って個々人の思う「関数型」には差異があります。これは言語設計者にも言えることです。「関数型」言語あるいは「関数型」の機能を持った言語は言語設計者の思う「関数型」を体現したものです。
時々「その定義だと○○という言語は関数型ではなくなる」「その定義だと△△という言語まで関数型言語になってしまう」という言葉を見かけますが、それはその言語設計者が思う「関数型」との差異があったというだけで、言語設計者を絶対視した原理主義者の言葉に他なりません。
「関数型」という単語は一定のコンセンサスを以って用いるには便利ですが、定義がない以上乱用は混乱を招きます(言語設計者も例外ではありません)。誰かを絶対視した原理主義者にはなりたくないものです。
Rubyで正規表現を使った抽出
rubyって楽で良いですよね。今日もその良さを感じたのでお裾分け。
最初にマッチした部分を取得したいことってよくありますよね。
文字列の中からfooの後に連なった最初の数字を取得したいとします。冗長に書くとこんな感じですかね?
if match = "foo123bar456".match(/foo(\d+)/) digit = match[1] end
String#matchを使ってもString#=~を使ってもどっちみち$1などは作られるのでさっさと=~と$1を使ったコードにしましょう。マッチを行う場所とマッチした部分を使う場所が遠いでもない限りあまり問題にはならないことが多いでしょう。でも良い子のみんなは時と場合を選びましょう。
if "foo123bar456" =~ /foo(\d+)/ digit = $1 end
こんなコード書こうものなら熟練rubyistからのマサカリが飛んできそうです。すばやく後置ifに書き換えます。
digit = $1 if "foo123bar456" =~ /foo(\d+)/
これでifの外からでもdigitが使えますね。これで熟練rubyistもニッコリです。
いやいや、コードを左から読んでいると真っ先に$1という部分が来て見た目がなんかアレです。
digit = "foo123bar456" =~ /foo(\d+)/ && $1
はい。みんな大好き&&
さんです。後置ifとどちらを使うかは時と場合を選んでください(二度目)。ruby使い以外からしたらトリッキーなコードですね。
え?マッチしなかった時はどうする?一行でif-then-end
書いた方がマシ?
みんな違ってみんな良いということですね!!!
寒いノリでくだらないネタ書いたことを反省している。夜のテンション怖い。
Haskellで正規表現
- 最初にマッチした部分を取得
firstCapture :: String -> String -> String firstCapture source pattern = head . mrSubList $ source =~ pattern firstCapture "abcdefg" =~ "a(\\w\\w)d\\w\\w) == "bc"
matchResultでは最初にマッチしたものだけを補足するので、[[String]]
でマッチさせて、探した方が良いかも。
括弧が1つならhead . head $ source =~ pattern
- パターン中の特殊文字
"123abc" =~ "\\d\\d\\d\\w\\w\\w"
\\
を付ける。
Tera TermでDockerにattachしたら面白かった(小並感
Tera Termを複数開いて、同じサーバにログイン後、同じコンテナにattachするとコンソールが同期する。
それだけ。
(.) . (.) の型
初めましての方が多いかもしれません。にわかプログラマのtechno_tanoCです。
前にふとghciで
:t (.) . (.)
と打ち込んでみたところ、
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
と表示されました。
にわかの私はこれだけでもう大混乱です。(b -> c)って何だ!(a -> a1 -> b)ってどこから来たんだ!
どういう超自然的法則によってこのような型になっているのか理解できなかった、というか今でもあまりよく分かっていませんが、自分の理解を深めるためにも頑張って説明してみます。
どこかしら間違えている可能性が高いのでマサカリやツッコミ、罵倒をして頂けると喜びます。
何はともあれとりあえず (.) の定義を確認してみます。
HackageのPreludeを見てみると、 (.) は
(.) :: (b -> c) -> (a -> b) -> a -> c (.) f g = \x -> f (g x)
のように定義されていました。
(b -> c)という型のfと、(a -> b) という型のgを受け取って、\x -> f (g x)しています。
まずは(.) . (.)の真ん中の . をこの定義から変形してみます。
(.) . (.) -- ↓ \f -> (.) ((.) f)
(.)は第一引数としてb -> cという型の関数を要求していますので、xではなくfと書いてみました。
ということでfはj -> kという型です。(定義のa, b, cと紛らわしいのでj, kとします)
\f -> (.) ((.) f) の一つ目の(.)は前置記法になっているので、ラムダ式を使ってこれを中置記法にしてみます。
\f -> (.) ((.) f) -- ↓ \f -> (\p -> ((.) f) . p) -- ↓ 括弧を外す \f -> \p -> ((.) f) . p -- ↓ 二引数関数にしてもHaskellなら勝手にカリー化してくれるので同じはず \f p -> ((.) f) . p
p が何者なのかはちょっと置いておいて、見た感じでは「fとpを取って、((.) f)とpを合成する関数」のように見えます。
f は j -> k という型なので、「(. :: (j -> k) -> (i -> j) -> i -> k) f」 は「 (.) に f を部分適用したもの」で、型は (i -> j) -> i -> k です。
(i -> j) -> i -> kと何か謎のpを合成するのですが、
定義では(.)は (b -> c) -> (a -> b) -> a -> cという型で、定義での(b -> c)の部分には \f p -> ((.) f) . pにおける (.) fの(i -> j) -> i -> kという型で決まってしまっています。
つまり
(以下自信無い)
bの部分が(i -> j)で、cが(i -> k)ということになる(はず)。
したがって(b -> c)の部分の型は(i -> j) -> (i -> k)です(多分)。
さて、ここでpはどのような型でなければならないでしょうか。
b -> c
↓
(i -> j) -> (i -> k)
のように対応しているので、
a -> b
より、p は
m -> (i -> j)
である必要があります。
よって
\f p -> ((.) f) . p :: (j -> k) -> (m -> (i -> j)) -> m -> i -> k
です。
(m -> (i -> j))は括弧を外して (m -> i -> j)になり、
\f p -> ((.) f) . p :: (j -> k) -> (m -> i -> j) -> m -> i -> k
これは、最初の
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
と同じ形です。
説明終わり。
なんというか説明というより、「こうやったらなんか知らないけど同じ形になった」というだけな感じもしますが、今回は以上です。
(a -> a1 -> b)の部分は「関数合成の都合上、受け渡し先が関数を要求してるから関数を返す関数、つまり二引数関数だったから」ということなのかな?
ζ*'ヮ')ζ
ζ*'ヮ')ζ<うっうー!