読者です 読者をやめる 読者になる 読者になる

by shigemk2

当面は技術的なことしか書かない

第2期 第15回 H本読書会 in 秋葉原

Haskell 勉強会

readhbon.doorkeeper.jp

おさらい 第9章読了、第10章読了

  • サンク 遅延評価の塊で、遅い→バイトストリングを使う
  • 正格バイトストリング 遅延バイトストリング
  • 正格版のほうが即時実行されるので、速い

  • 逆ポーランド記法電卓(RPN)

  • 4 3 +→7
  • 実際に手順を踏んで書く
  • RPN

gist.github.com

f:id:shigemk2:20150624202426p:plain

  • ヒースロー空港からロンドンへの道順

f:id:shigemk2:20150624202502p:plain

  • Haskell関係なく解く手順を考える
  • コードに起こす
  • 少しずつ仕上げていく

gist.github.com

第11章 ファンクターからアプリカティブファンクターへ

  • 純粋性
  • 高階関数
  • 型引数
  • 代数的データ型
  • 型クラス

→簡単に多相性を実装できる

  • 型クラスはオープン。つまりデータ型を定義し、その型がどう振る舞うかを考えてから、その振る舞いを定義する型クラスに属させることができる

  • ファンクター

  • アプリカティブファンクター

11.1 帰ってきたファンクター

 fmap :: (a -> b) -> f a -> f b
  • ファンクターは、文脈を持った値
  • その型コンストラクタの種類(kind)は * -> * でないといけない

ファンクターとしての I/O アクション

  • /O アクションとは小さな足のはえた箱で、外の世界に出かけていって何か値を入れて帰ってきてくれるのだ、という比喩
  • I/Oアクションにfmap
instance Functor IO where
    fmap f action = do
        result <- action
        return (f result)
  • return とは、ご存知のとおり「特に仕事を行わず、ただ何かを結果として提示する I/O アクション」を作る関数

gist.github.com

  • 箱から中身を取り出して、中身をごにょごにょして、また箱に戻すイメージ。ので、実際に返ってくるのはI/Oアクション。
  • I/O という箱の中に入っている値に関数を適用できるわけですが、 fmapした結果も IO なので、そいつもやはり外の世界に出かけていって何か値を取ってくるという動作

  • ファンクターの中身を、 1 つではなく複数の関数を使って写したいなら、そのための関数をトップレベルで宣言してもいいし、ラムダ式を使ってもいいですが、一番いいのは関数合成

  • ラムダを使ってもポイントフリーを使っても良い(ポイントフリーは結構Haskellライク)

fmapに複数の関数を応用した例 gist.github.com

$ ./fmapping_io
^[[3~^[[3~^[[3~^[[3~^[[3~ほげ
げ-ほ-~-3-[--3-[--3-[--3-[--3-[-

intersperse - Hoogle

ファンクターとしての関数

  • r -> a は、 (->) r a と書き換えることができる
  • Functor のインスタンスにする型コンストラクタは引数が 1 つである必要がある
  • 関数合成がコレにあたる

関数に見えているものが型であるという謎

  • 関数がオブジェクトだとして、型をもつと、Function a bという型になる
  • Haskellでは(->) a bという記法になる

  • let func = fmap (*2) (+1)

  • 普通に合成された関数が返るので、func 3を呼び出せば使える
Prelude> let func = fmap (*2) (+1)
Prelude> func 3
8
Prelude> func 4
10
Prelude> let x2 x = x + x
Prelude> let x4 = fmap x2 x2
Prelude> x4 4
16
Prelude> x2 3
6

Functorのなかまたち

Prelude> :i Functor
class Functor f where
  fmap :: (a -> b) -> f a -> f b
  (GHC.Base.<$) :: a -> f b -> f a
        -- Defined in `GHC.Base'
instance Functor (Either a) -- Defined in `Data.Either'
instance Functor Maybe -- Defined in `Data.Maybe'
instance Functor [] -- Defined in `GHC.Base'
instance Functor IO -- Defined in `GHC.Base'
instance Functor ((->) r) -- Defined in `GHC.Base'
instance Functor ((,) a) -- Defined in `GHC.Base'
Prelude> fmap (*2) (Just 3)
Just 6
Prelude> fmap (show) (Just 3)
Just "3"
Prelude> fmap (+1) (Right 3)
Right 4
Prelude> fmap (+1) (Left 3)
Left 3
Prelude> fmap (show) [1,2,3,4]
["1","2","3","4"]
Prelude> fmap (+1) [1,2,3,4]
[2,3,4,5]
Prelude> fmap (show.+1) [1,2,3,4]

<interactive>:7:11:
    Not in scope: `.+'
    Perhaps you meant one of these:
      `++' (imported from Prelude), `.' (imported from Prelude),
      `+' (imported from Prelude)
Prelude> fmap (show.(+1)) [1,2,3,4]
["2","3","4","5"]
Prelude> ((fmap (show) ((+) 3)) 4 )
"7"
Prelude> :t (fmap (show) ((+) 3)) 4 
(fmap (show) ((+) 3)) 4 :: String
Prelude> fmap (+31) ("aaa",2)
("aaa",33)
  • fmap を関数に適用すると関数合成になる
  • 「ある関数」を使って「何らかの計算」を写すと、得られるものは「似たような計算」ですが、計算結果はその「関数」で修飾されたものになっている
  • Haskell の関数は実はすべて1 引数関数とみなせる、というカリー化の概念
  • 関数を取って「元の関数に似ているけどファンクター値を取ってファンクター値を返す関数」を返す関数だと思うこともできます。 fmap は、関数 a -> b を取って、関数 f a -> f b を返すのです。こういう操作を、関数の持ち上げ(lifting)といいます。
Prelude> :t fmap (+2)
fmap (+2) :: (Functor f, Num b) => f b -> f b
  • 「関数でファンクター値を写す」ことと、「関数を持ち上げてからファンクター値に適用する」ことは等価
  • 写像と持ち上げ
Prelude> :k (->) 
(->) :: * -> * -> *

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!