SlideShare uma empresa Scribd logo
1 de 49
Baixar para ler offline
すごい配列楽しく学ぼう
     ~ Haskell の配列事情 ~
           @xenophobia__




        
テーマについて
勉強会をやるらしい
→ 各 λ 人の好きな題材でいいらしい。
→ じゃあ Haskell で何か話そう。
→ でもすごい H 本出たし基本的なことはもう話題と
 して出尽してるよな……
→ あれ?この本配列について書いてなくね?
→ λ < Haskell の配列も楽しく学ぼう!



              
Haskell とは!
   なんてことを話している時間はない。
   とりあえず今後の話に関わることとしてこれぐらい。
            型に厳しい(強い静的型付けである)
                  キャストとかゆとりな事はしない
                  型が合ってないと絶対許してくれない
            参照透明性が(基本的には)成り立つ
                  IO ・破壊的変更の扱いがめんどい特殊
                  unsafe ほげほげの話はしない
            「型クラス」がある
                  「型クラス」は共通の性質を持った型の集まり
                  ある型クラスに関して polymorphic な関数とか書ける
                         
Haskell の配列について
   Haskell では、コレクションは普通リスト型で扱う。
   配列はあんまり話題にならない気がする(すごい H
     本にも記述は多分ない? RHW にはあった)
   調べるといろいろ (Array, UArray, IArray, MArray, 
     IOArray, ...) でてきてよくわからない。


   でも……
             プロコンとかだと使いたい。
             できるだけ速いほうがいい。
                     
Agenda
   とりあえず普通の配列( Array, UArray )
   O(1) で変更できなきゃ配列じゃない! (IOArray, 
     IOUArray)
   型クラスの話をしよう (IArray, MArray)




                 
Agenda
   とりあえず普通の配列( Array, UArray )
   O(1) で変更できなきゃ配列じゃない! (IOArray, 
     IOUArray)
   型クラスの話をしよう (IArray, MArray)




                 
Haskell 標準の配列を使う
   Array, UArray (違いはあとで。)
   Array は Data.Array( または Data.Array.IArray) に
     ある。
   UArray は Data.Array.Unboxed にある。




                    
Haskell 標準の配列を使う
   配列を作るには?
    → array 関数を使う(或いは listArray )
             array (0, 3) [(0, 1), (1, 2), (2, 4), (3, 9)]
             array ((1, 1), (2, 2)) [((1,1), 'a'), ((1,2), 'a'), ((2,1), 'z'), 
                ((2,2), 'd')]
             listArray (1, 100) [0, 0 ..]
   インデックスには、 Int, Char 及びそのタプルなどが
     使える。
                       厳密には Ix 型クラスのインスタンスがすべて使える。
                         ここでは説明は省略。
                                 
Haskell 標準の配列を使う
   型は?
   C などと違い、インデックスもいろいろ使える。
      → インデックスも型パラメタとして与える。
   インデックスが Int で要素が String の Array なら、
     Array Int String  となる。
   インデックスが (Char, Char) で要素が Int の
     UArray なら、 UArray (Char, Char) Int となる。



                    
Haskell 標準の配列を使う
   配列 ary の要素にアクセスしたい → (!) 演算子
             例) let x = ary!2 in …
                     let x = ary!(0, 3) in …
   配列 ary の要素を更新したい → (//) 演算子
             例) let ary' = ary//[(1, 2), (3, 0)]
                     ­­ ary[1] を 2 に、 ary[3] を 0 に更新
             ただし、これには後に述べるように罠が隠れている。




                               
Array/UArray の違いって?
   Array 型と UArray 型は、使い方はほぼ同じ。
   じゃあ違いは?
             Array は Box 化されている
             Uarray は Box 化されていない


   (´ ・ _ ・ `) ? < おまえは なにを いってるんだ




                        
Array/UArray の違いって?
   つまり
            Array は、オブジェクトへのポインタを持った配列
            UArray は、値そのものを入れる配列
   もっと言うと
            Array は Lazy な配列
            UArray は Strict な配列




                         
Array/UArray の違いって?
   Array の要素を参照するときの動作
   ポインタが示す先に行き、オブジェクトを返す
               そのオブジェクトの値が必要になったら評価する。
               もちろん、オブジェクトが評価済みならその値自体を
                 返す。
    0

    1                     未評価              評価済み
                                    Eval
    2
                          10 * 10           100
    3




                       
なんでそんなことになってんの?
   知らん。




            
なんでそんなことになってんの?
   …… ではさすがにあんまりなので。


   Array が正格評価する仕様だと、例えば変数 x, y, 
     z を用いて
          ary = array (1, 3) [(1, x), (2, y), (3, z)]
    のようにに配列を作ると、 x, y, z は配列作成時に
     全て評価されなければならない。(評価しないと
     格納する値がわからない)


                           
なんでそんなことになってんの?
   「 x を配列に入れる」というロジックに「 x を評価す
      る」ことは必要か?
   たとえば ary のうち一つの要素にしかアクセスしな
     いかもしれない。
    → 「アクセスしない=使わない」ってことじゃない
     の?




               
なんでそんなことになってんの?
   λ <使わないのに評価されるのは遅延評価とし
     ておかしい!( Haskell は call by need の遅延評
     価!)
   要素が何であるかの評価は、実際にその要素の値
     が必要になるまで遅延するのがよい!
               てな感じなんじゃないですかね。知らんけど……




                  
UArray(Unboxed Array)
   Q. じゃあ UArray って何?
   A. Array 遅い!って人のために。


   UArray はポインタを介さない分 Array よりアクセ
     スが速い&場所とらない。
   速さとヒープ領域を気にするならこっち使いましょ
     う。



                  
Agenda
   とりあえず普通の配列( Array, UArray )
   O(1) で変更できなきゃ配列じゃない! (IOArray, 
     IOUArray)
   型クラスの話をしよう (IArray, MArray)




                 
「 Array と参照透明性」
             に関する孔明の罠
   さっき (//) という「更新」演算子がでてきた。
   でも、 Haskell の関数は(基本的には)参照透明性
     が成り立ってなければならない。
   参照透明性 : 関数と引数の組によって、返り値が
     一意に決まる。




               
「 Array と参照透明性」
                     に関する孔明の罠
   つまり?
   ary = array (1, 2) [(1, 1), (2, 2)]
    として、
               ary!1
               let ary' = ary//[(1, 2)] in
                  ary!1
   は同じ値であってほしいなければならない。



                                 
「 Array と参照透明性」
              に関する孔明の罠
   しかし、 ary を破壊的に更新してしまった場合、
     ary!1 が元の値を返すかどうかはわからない。
     ary!1 が更新前に評価されれば 1 が返り、
     ary!1 が更新後に評価されれば 2 が返る。
          → 参照透明性に反する!
          ary!1 はいつ評価しても同じ値を返さなければなら
            ない。




                  
「 Array と参照透明性」
             に関する孔明の罠
   じゃあどういう実装になってんの?
     → 単純に、 ary には変更を加えず
     ary' 用の配列を新しく作る。
     → これでどこをいつ参照しても矛盾は起きない。
     やったね(?)




               
「 Array と参照透明性」
                 に関する孔明の罠
   …… なにもうまくいってません。
            問題 1 :そもそも「更新」じゃなくて「新規作成」演算
              子になってる
            問題 2 :計算量 O(sizeof(ary))
              →  プロコンでうっかり使ったりすると死ぬ
   結論: (//) は更新する演算子ではなく、更新され
     た配列を新規作成する演算子




                     
補足
   注 1 )ドキュメントにも Incremental array updates
     と書いてあってまぎらわしい。
   注 2 ) Data.Array.IArray には、「殆どの IArray の
     インスタンスにおいて更新には O(N) かかる」と
     書いてある。
   注 3 ) Data.Array.IArray の (//) 関数の仕様自体が
     O(N) の計算量に本質的に関わっているわけで
     はない。(実際、もっと速い更新を実現する
     DiffArray などの IArray インスタンスもある)

                   
うまい解決法はないか
   破壊的更新ができれば文句ない。
   Haskell で破壊的操作をうまく扱う方法として IO
     モナドがある。
   IO モナドの文脈中ならうまいこと破壊的操作を扱
      える。
    → IO モナドの中でしか使えない配列を作れば万
     事解決!




               
IOArray/IOUArray
   Data.Array.IO にどっちも定義されている。
   作成・参照・更新は全て IO アクション。
     → アクションの実行順序は保証されるので、参
     照と更新の順序が入れかわったりしない!




                
IOArray/IOUArray
   作成: newArray(newArray_, newListArray)
              newArray (lb, ub) x  で、インデックスが lb 〜 ub の
                 x で初期化された配列を返すアクションを返す。
   参照: readArray
              readArray ary i で、 ary の i 番地の値を返すアクショ
                 ンを返す。
   更新: writeArray
              writeArray ary i x で、 ary の i 番地の値を x に変更
                するアクションを返す。

                           
IOArray/IOUArray
   使い方
   IO モナドの中で使う。
             do ary ← newArray (1, 10) 0
                  x ← readArray ary 1
                  writeArray ary 1 3
                  y ← readArray ary 1
                  ...
   x, y  には 0, 3 がそれぞれ束縛される。



                            
参照透明性は?
   さっきのプログラムの 2 つの readArray ary 1 って
     別々の値を返してね?
     → readArray ary 1 はあくまで両方「 ary からイン
     デックス i の値を取り出す操作」を表すアクション
     なのであって、 x, y を返す関数ではありません。




                 
Pros & Cons
   正真正銘の O(1) 更新が可能!
   ただ、 IO に汚染される上、インタフェースが多少め
     んどい。




              
備考
   IOArray 以外にも、 STArray など破壊的代入がで
      きる配列はある。
   IO モナドと違い ST モナドは外に脱出できるの
      で、 IO 操作を他に使わないならこっちのほうが
      いいかも。
             IOArray と使い勝手は一緒




                       
補足( 1 )
   thaw/freeze 関数を用いて普通の配列と可変配列
      との相互変換もできる。
   thaw (解凍)が Immutable → Mutable の変換
   freeze (凍結)が Mutable → Immutable の変換




                  
補足( 2 )
   もちろん thaw も freeze も O(N) 。
              ドキュメントにもちゃんと” by taking a complete copy 
                of it.” とある。
   こういうときこそ STArray 使うといいかもしれない。
              手前味噌ですが STUArray を用いたエラトステネス
                の篩実装例
                http://d.hatena.ne.jp/g940425/20110827/1314442246
                      後半で unsafe な関数使ってるけど……こういう使い
                        かたなら問題ないはず。



                             
Agenda
   とりあえず普通の配列( Array, UArray )
   O(1) で変更できなきゃ配列じゃない! (IOArray, 
     IOUArray)
   型クラスの話をしよう (IArray, MArray)




                 
抽象化したい!
   Uarray と Array 、 IOUArray と IOArray は Box 化 /
     非 Box 化の違いでしかない。
     → これらの違いを無視した一般的なインタ
     フェースの関数を書きたい。




                    
例えば……




   sumA : Int 要素を持つ Array の全要素の和



                
例えば……




   sumA をそのまま UArray に使おうとすると、当然の
      ように型エラーで怒られる。


               
例えば……




   型ごとに関数を用意すればお k !



              
いやいや
      <「お k !」じゃねえ!
       関数本体全く同じじゃねえか!
   こういう、形がほとんど同じ関数を連ねるのを
     「ボイラープレート( boilerplate )」という
     → Haskeller が最も嫌うもの
                   私も大嫌いです



   形が同じなら、まとめてかけるはず



                
IArray/MArray
   変更不可配列 (Immutable Array) を抽象する型ク
     ラスが IArray
   可変配列 (Mutable Array) を抽象する型クラスが
     MArray
   型クラス: Iarray/MArray はそれ自体「型」ではな
     い( IArray  〜 型の値、は存在しない)




                
IArray/MArray
   IArray a e : e 型の要素を持つ変更不可配列 a
   Marray a e m : m モナドのコンテクストで変更可能
     な、 e 型の要素を持つ配列 a




                
例ふたたび




   IArray を使って sumA を抽象化
   GHC 拡張 FlexibleContexts が必要(理由は後述)


                 
例ふたたび
   sumA :: IArray a Int => …
    → 「 a が Int を要素に持つ変更不可配列」
     という型制約をかけている
   sumA :: … => a Int Int → Int
      → a Int Int → Int という型を持つので、
      a として当てはまる(= IArray a Int というインス
      タンスが宣言されている)もの全てに適用可能




                      
例ふたたび
   Data.Array.IArray にインスタンスとして
             IArray Array e
             IArray UArray Int
    が宣言されている。( e は型変数で、なんでもよい)
     → a が Array でも UArray でも型が合う!
             IArray Array Int => Array Int Int → Int
               OK !:インスタンス IArray Array e がある


             IArray UArray Int => UArray Int Int → Int
               OK !:インスタンス IArray UArray Int がある



                              
補足( 3 )
   Q. かける制約は IArray a e でよかったのでは?
   A. 実は、 IArray UArray e という要素の型 e につい
     て一般化されたインスタンスはない。
     (Data.Array.UArray のドキュメント参照)
     → なのでこの例では IArray a Int のインスタンス
     宣言を使っており、そのため制約が IArray a Int
     となっている。
             IArray Array e => Array Int Int → Int
               OK !:インスタンス IArray Array e がある


             IArray UArray e => UArray Int Int → Int
               NG !:インスタンス IArray UArray e はない
                             
補足( 4 )
   本来、型制約に具体的な型を入れることはできな
     い。
     → IArray a Int という制約はふつう許されない。
   これを許可するのが FlexibleContexts 拡張




                 
総括
   Haskell でも普通に配列使える
   しかし、速いプログラムにするには工夫が要る
     →使えるところでは Unboxed な配列を使う、とか
   抽象化もできる
     → ただ、なんでもかんでも抽象化すればいいと
     いうわけじゃない。 Lazy/Strict が本質的な場面
     もあるだろうし、競技プログラミングでわざわざ
     使いもしないのに抽象化した関数を書くのは得
     策でない。


               
より進んだ話題
   Haskell には並列処理用行列
     repa ( [hackage]http://hackage.haskell.org/packa
     ge/repa )という、データ並列プログラミングをサ
     ポートする特殊なライブラリもある。
     → 配列というよりは「行列」
     → ついこの間( 2012/9/24 ) GHC­7.6 にも対応
   DiffArray なんてのもある( Immutable かつ更新も
     それなりに速い)
     [hackage]http://hackage.haskell.org/package/diffa
     rray

                      

Mais conteúdo relacionado

Mais procurados

プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法
Takuya Akiba
 
勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは
Takuya Akiba
 

Mais procurados (20)

プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法プログラミングコンテストでの動的計画法
プログラミングコンテストでの動的計画法
 
文字列アルゴリズム
文字列アルゴリズム文字列アルゴリズム
文字列アルゴリズム
 
キメるClojure
キメるClojureキメるClojure
キメるClojure
 
関数型プログラミング入門 with OCaml
関数型プログラミング入門 with OCaml関数型プログラミング入門 with OCaml
関数型プログラミング入門 with OCaml
 
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
プログラミングコンテストでのデータ構造 2 ~平衡二分探索木編~
 
ブースティング入門
ブースティング入門ブースティング入門
ブースティング入門
 
今から始める Lens/Prism
今から始める Lens/Prism今から始める Lens/Prism
今から始める Lens/Prism
 
数学プログラムを Haskell で書くべき 6 の理由
数学プログラムを Haskell で書くべき 6 の理由数学プログラムを Haskell で書くべき 6 の理由
数学プログラムを Haskell で書くべき 6 の理由
 
何となく勉強した気分になれるパーサ入門
何となく勉強した気分になれるパーサ入門何となく勉強した気分になれるパーサ入門
何となく勉強した気分になれるパーサ入門
 
指数時間アルゴリズム入門
指数時間アルゴリズム入門指数時間アルゴリズム入門
指数時間アルゴリズム入門
 
C++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISるC++コミュニティーの中心でC++をDISる
C++コミュニティーの中心でC++をDISる
 
F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~
 
目指せグラフマスター
目指せグラフマスター目指せグラフマスター
目指せグラフマスター
 
Scala 初心者が米田の補題を Scala で考えてみた
Scala 初心者が米田の補題を Scala で考えてみたScala 初心者が米田の補題を Scala で考えてみた
Scala 初心者が米田の補題を Scala で考えてみた
 
明日使えないすごいビット演算
明日使えないすごいビット演算明日使えないすごいビット演算
明日使えないすごいビット演算
 
ウェーブレット木の世界
ウェーブレット木の世界ウェーブレット木の世界
ウェーブレット木の世界
 
たのしい関数型
たのしい関数型たのしい関数型
たのしい関数型
 
不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarray不遇の標準ライブラリ - valarray
不遇の標準ライブラリ - valarray
 
勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは勉強か?趣味か?人生か?―プログラミングコンテストとは
勉強か?趣味か?人生か?―プログラミングコンテストとは
 
証明プログラミング超入門
証明プログラミング超入門証明プログラミング超入門
証明プログラミング超入門
 

Destaque

Ekmett勉強会発表資料
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料
時響 逢坂
 
すごいHaskell読書会 第六章 発表資料
すごいHaskell読書会 第六章 発表資料すごいHaskell読書会 第六章 発表資料
すごいHaskell読書会 第六章 発表資料
Hiromasa Ohashi
 
すごいVimでhaskellを書こう@なごやまつり
すごいVimでhaskellを書こう@なごやまつりすごいVimでhaskellを書こう@なごやまつり
すごいVimでhaskellを書こう@なごやまつり
cohama
 
altJS勉強会「Haxeすごいからみんな使え!」
altJS勉強会「Haxeすごいからみんな使え!」altJS勉強会「Haxeすごいからみんな使え!」
altJS勉強会「Haxeすごいからみんな使え!」
政樹 尾野
 

Destaque (20)

Xenophobia
XenophobiaXenophobia
Xenophobia
 
Xenophobia
XenophobiaXenophobia
Xenophobia
 
Xenophobia Talk
Xenophobia TalkXenophobia Talk
Xenophobia Talk
 
Diff
DiffDiff
Diff
 
Ekmett勉強会発表資料
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料
 
Ekmett勉強会発表資料
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料
 
Windows 8 UX Guidelines
Windows 8 UX GuidelinesWindows 8 UX Guidelines
Windows 8 UX Guidelines
 
Xenophobia 2
Xenophobia 2Xenophobia 2
Xenophobia 2
 
すごいHaskell読書会 第六章 発表資料
すごいHaskell読書会 第六章 発表資料すごいHaskell読書会 第六章 発表資料
すごいHaskell読書会 第六章 発表資料
 
ウォーキングとゆっくり呼吸のすごい効果
ウォーキングとゆっくり呼吸のすごい効果ウォーキングとゆっくり呼吸のすごい効果
ウォーキングとゆっくり呼吸のすごい効果
 
Yesod勉強会
Yesod勉強会Yesod勉強会
Yesod勉強会
 
INTERCULTURAL COMMUNICATION COMPETENCE
INTERCULTURAL COMMUNICATION COMPETENCEINTERCULTURAL COMMUNICATION COMPETENCE
INTERCULTURAL COMMUNICATION COMPETENCE
 
【アップロードテスト】データマイニング入門編_201501
【アップロードテスト】データマイニング入門編_201501【アップロードテスト】データマイニング入門編_201501
【アップロードテスト】データマイニング入門編_201501
 
すごいVimでhaskellを書こう@なごやまつり
すごいVimでhaskellを書こう@なごやまつりすごいVimでhaskellを書こう@なごやまつり
すごいVimでhaskellを書こう@なごやまつり
 
プラグインだけじゃない!そのままでもすごいvim
プラグインだけじゃない!そのままでもすごいvimプラグインだけじゃない!そのままでもすごいvim
プラグインだけじゃない!そのままでもすごいvim
 
「360°スゴイ」を創るVOYAGE GROUPエンジニア成長施策
「360°スゴイ」を創るVOYAGE GROUPエンジニア成長施策「360°スゴイ」を創るVOYAGE GROUPエンジニア成長施策
「360°スゴイ」を創るVOYAGE GROUPエンジニア成長施策
 
Globalization Of Health Care Final Star
Globalization Of Health Care Final StarGlobalization Of Health Care Final Star
Globalization Of Health Care Final Star
 
すごいタスク管理(仮)
すごいタスク管理(仮)すごいタスク管理(仮)
すごいタスク管理(仮)
 
altJS勉強会「Haxeすごいからみんな使え!」
altJS勉強会「Haxeすごいからみんな使え!」altJS勉強会「Haxeすごいからみんな使え!」
altJS勉強会「Haxeすごいからみんな使え!」
 
このIRのグラフがすごい!上場企業2015
このIRのグラフがすごい!上場企業2015このIRのグラフがすごい!上場企業2015
このIRのグラフがすごい!上場企業2015
 

Semelhante a すごい配列楽しく学ぼう

BOF1-Scala02.pdf
BOF1-Scala02.pdfBOF1-Scala02.pdf
BOF1-Scala02.pdf
Hiroshi Ono
 
BOF1-Scala02.pdf
BOF1-Scala02.pdfBOF1-Scala02.pdf
BOF1-Scala02.pdf
Hiroshi Ono
 
BOF1-Scala02.pdf
BOF1-Scala02.pdfBOF1-Scala02.pdf
BOF1-Scala02.pdf
Hiroshi Ono
 

Semelhante a すごい配列楽しく学ぼう (11)

BOF1-Scala02.pdf
BOF1-Scala02.pdfBOF1-Scala02.pdf
BOF1-Scala02.pdf
 
BOF1-Scala02.pdf
BOF1-Scala02.pdfBOF1-Scala02.pdf
BOF1-Scala02.pdf
 
BOF1-Scala02.pdf
BOF1-Scala02.pdfBOF1-Scala02.pdf
BOF1-Scala02.pdf
 
scala.collection 再入門 (改)
scala.collection 再入門 (改)scala.collection 再入門 (改)
scala.collection 再入門 (改)
 
Scala勉強会
Scala勉強会Scala勉強会
Scala勉強会
 
Equality in Scala (ScalaMatsuri 2020)
Equality in Scala (ScalaMatsuri 2020)Equality in Scala (ScalaMatsuri 2020)
Equality in Scala (ScalaMatsuri 2020)
 
Javaプログラミング入門【第7回】
Javaプログラミング入門【第7回】Javaプログラミング入門【第7回】
Javaプログラミング入門【第7回】
 
Scalaz
ScalazScalaz
Scalaz
 
How wonderful to be (statically) typed 〜型が付くってスバラシイ〜
How wonderful to be (statically) typed 〜型が付くってスバラシイ〜How wonderful to be (statically) typed 〜型が付くってスバラシイ〜
How wonderful to be (statically) typed 〜型が付くってスバラシイ〜
 
RSpecのここがすごい!
RSpecのここがすごい!RSpecのここがすごい!
RSpecのここがすごい!
 
ゆるふわScalaコップ本読書会 第7章
ゆるふわScalaコップ本読書会 第7章ゆるふわScalaコップ本読書会 第7章
ゆるふわScalaコップ本読書会 第7章
 

すごい配列楽しく学ぼう

  • 1. すごい配列楽しく学ぼう ~ Haskell の配列事情 ~ @xenophobia__    
  • 2. テーマについて 勉強会をやるらしい → 各 λ 人の好きな題材でいいらしい。 → じゃあ Haskell で何か話そう。 → でもすごい H 本出たし基本的なことはもう話題と して出尽してるよな…… → あれ?この本配列について書いてなくね? → λ < Haskell の配列も楽しく学ぼう!    
  • 3. Haskell とは!  なんてことを話している時間はない。  とりあえず今後の話に関わることとしてこれぐらい。  型に厳しい(強い静的型付けである)  キャストとかゆとりな事はしない  型が合ってないと絶対許してくれない  参照透明性が(基本的には)成り立つ  IO ・破壊的変更の扱いがめんどい特殊  unsafe ほげほげの話はしない  「型クラス」がある  「型クラス」は共通の性質を持った型の集まり  ある型クラスに関して polymorphic な関数とか書ける    
  • 4. Haskell の配列について  Haskell では、コレクションは普通リスト型で扱う。  配列はあんまり話題にならない気がする(すごい H 本にも記述は多分ない? RHW にはあった)  調べるといろいろ (Array, UArray, IArray, MArray,  IOArray, ...) でてきてよくわからない。  でも……  プロコンとかだと使いたい。  できるだけ速いほうがいい。    
  • 5. Agenda  とりあえず普通の配列( Array, UArray )  O(1) で変更できなきゃ配列じゃない! (IOArray,  IOUArray)  型クラスの話をしよう (IArray, MArray)    
  • 6. Agenda  とりあえず普通の配列( Array, UArray )  O(1) で変更できなきゃ配列じゃない! (IOArray,  IOUArray)  型クラスの話をしよう (IArray, MArray)    
  • 7. Haskell 標準の配列を使う  Array, UArray (違いはあとで。)  Array は Data.Array( または Data.Array.IArray) に ある。  UArray は Data.Array.Unboxed にある。    
  • 8. Haskell 標準の配列を使う  配列を作るには? → array 関数を使う(或いは listArray )  array (0, 3) [(0, 1), (1, 2), (2, 4), (3, 9)]  array ((1, 1), (2, 2)) [((1,1), 'a'), ((1,2), 'a'), ((2,1), 'z'),  ((2,2), 'd')]  listArray (1, 100) [0, 0 ..]  インデックスには、 Int, Char 及びそのタプルなどが 使える。  厳密には Ix 型クラスのインスタンスがすべて使える。 ここでは説明は省略。    
  • 9. Haskell 標準の配列を使う  型は?  C などと違い、インデックスもいろいろ使える。 → インデックスも型パラメタとして与える。  インデックスが Int で要素が String の Array なら、 Array Int String  となる。  インデックスが (Char, Char) で要素が Int の UArray なら、 UArray (Char, Char) Int となる。    
  • 10. Haskell 標準の配列を使う  配列 ary の要素にアクセスしたい → (!) 演算子  例) let x = ary!2 in …        let x = ary!(0, 3) in …  配列 ary の要素を更新したい → (//) 演算子  例) let ary' = ary//[(1, 2), (3, 0)] ­­ ary[1] を 2 に、 ary[3] を 0 に更新  ただし、これには後に述べるように罠が隠れている。    
  • 11. Array/UArray の違いって?  Array 型と UArray 型は、使い方はほぼ同じ。  じゃあ違いは?  Array は Box 化されている  Uarray は Box 化されていない  (´ ・ _ ・ `) ? < おまえは なにを いってるんだ    
  • 12. Array/UArray の違いって?  つまり  Array は、オブジェクトへのポインタを持った配列  UArray は、値そのものを入れる配列  もっと言うと  Array は Lazy な配列  UArray は Strict な配列    
  • 13. Array/UArray の違いって?  Array の要素を参照するときの動作  ポインタが示す先に行き、オブジェクトを返す  そのオブジェクトの値が必要になったら評価する。  もちろん、オブジェクトが評価済みならその値自体を 返す。 0 1 未評価 評価済み Eval 2 10 * 10 100 3    
  • 15. なんでそんなことになってんの?  …… ではさすがにあんまりなので。  Array が正格評価する仕様だと、例えば変数 x, y,  z を用いて ary = array (1, 3) [(1, x), (2, y), (3, z)] のようにに配列を作ると、 x, y, z は配列作成時に 全て評価されなければならない。(評価しないと 格納する値がわからない)    
  • 16. なんでそんなことになってんの?  「 x を配列に入れる」というロジックに「 x を評価す る」ことは必要か?  たとえば ary のうち一つの要素にしかアクセスしな いかもしれない。 → 「アクセスしない=使わない」ってことじゃない の?    
  • 17. なんでそんなことになってんの?  λ <使わないのに評価されるのは遅延評価とし ておかしい!( Haskell は call by need の遅延評 価!)  要素が何であるかの評価は、実際にその要素の値 が必要になるまで遅延するのがよい! てな感じなんじゃないですかね。知らんけど……    
  • 18. UArray(Unboxed Array)  Q. じゃあ UArray って何?  A. Array 遅い!って人のために。  UArray はポインタを介さない分 Array よりアクセ スが速い&場所とらない。  速さとヒープ領域を気にするならこっち使いましょ う。    
  • 19. Agenda  とりあえず普通の配列( Array, UArray )  O(1) で変更できなきゃ配列じゃない! (IOArray,  IOUArray)  型クラスの話をしよう (IArray, MArray)    
  • 20. 「 Array と参照透明性」 に関する孔明の罠  さっき (//) という「更新」演算子がでてきた。  でも、 Haskell の関数は(基本的には)参照透明性 が成り立ってなければならない。  参照透明性 : 関数と引数の組によって、返り値が 一意に決まる。    
  • 21. 「 Array と参照透明性」 に関する孔明の罠  つまり?  ary = array (1, 2) [(1, 1), (2, 2)] として、  ary!1  let ary' = ary//[(1, 2)] in ary!1  は同じ値であってほしいなければならない。    
  • 22. 「 Array と参照透明性」 に関する孔明の罠  しかし、 ary を破壊的に更新してしまった場合、 ary!1 が元の値を返すかどうかはわからない。 ary!1 が更新前に評価されれば 1 が返り、 ary!1 が更新後に評価されれば 2 が返る。 → 参照透明性に反する! ary!1 はいつ評価しても同じ値を返さなければなら ない。    
  • 23. 「 Array と参照透明性」 に関する孔明の罠  じゃあどういう実装になってんの? → 単純に、 ary には変更を加えず ary' 用の配列を新しく作る。 → これでどこをいつ参照しても矛盾は起きない。 やったね(?)    
  • 24. 「 Array と参照透明性」 に関する孔明の罠  …… なにもうまくいってません。  問題 1 :そもそも「更新」じゃなくて「新規作成」演算 子になってる  問題 2 :計算量 O(sizeof(ary)) →  プロコンでうっかり使ったりすると死ぬ  結論: (//) は更新する演算子ではなく、更新され た配列を新規作成する演算子    
  • 25. 補足  注 1 )ドキュメントにも Incremental array updates と書いてあってまぎらわしい。  注 2 ) Data.Array.IArray には、「殆どの IArray の インスタンスにおいて更新には O(N) かかる」と 書いてある。  注 3 ) Data.Array.IArray の (//) 関数の仕様自体が O(N) の計算量に本質的に関わっているわけで はない。(実際、もっと速い更新を実現する DiffArray などの IArray インスタンスもある)    
  • 26. うまい解決法はないか  破壊的更新ができれば文句ない。  Haskell で破壊的操作をうまく扱う方法として IO モナドがある。  IO モナドの文脈中ならうまいこと破壊的操作を扱 える。 → IO モナドの中でしか使えない配列を作れば万 事解決!    
  • 27. IOArray/IOUArray  Data.Array.IO にどっちも定義されている。  作成・参照・更新は全て IO アクション。 → アクションの実行順序は保証されるので、参 照と更新の順序が入れかわったりしない!    
  • 28. IOArray/IOUArray  作成: newArray(newArray_, newListArray)  newArray (lb, ub) x  で、インデックスが lb 〜 ub の x で初期化された配列を返すアクションを返す。  参照: readArray  readArray ary i で、 ary の i 番地の値を返すアクショ ンを返す。  更新: writeArray  writeArray ary i x で、 ary の i 番地の値を x に変更 するアクションを返す。    
  • 29. IOArray/IOUArray  使い方  IO モナドの中で使う。  do ary ← newArray (1, 10) 0   x ← readArray ary 1   writeArray ary 1 3   y ← readArray ary 1   ...  x, y  には 0, 3 がそれぞれ束縛される。    
  • 30. 参照透明性は?  さっきのプログラムの 2 つの readArray ary 1 って 別々の値を返してね? → readArray ary 1 はあくまで両方「 ary からイン デックス i の値を取り出す操作」を表すアクション なのであって、 x, y を返す関数ではありません。    
  • 31. Pros & Cons  正真正銘の O(1) 更新が可能!  ただ、 IO に汚染される上、インタフェースが多少め んどい。    
  • 32. 備考  IOArray 以外にも、 STArray など破壊的代入がで きる配列はある。  IO モナドと違い ST モナドは外に脱出できるの で、 IO 操作を他に使わないならこっちのほうが いいかも。  IOArray と使い勝手は一緒    
  • 33. 補足( 1 )  thaw/freeze 関数を用いて普通の配列と可変配列 との相互変換もできる。  thaw (解凍)が Immutable → Mutable の変換  freeze (凍結)が Mutable → Immutable の変換    
  • 34. 補足( 2 )  もちろん thaw も freeze も O(N) 。  ドキュメントにもちゃんと” by taking a complete copy  of it.” とある。  こういうときこそ STArray 使うといいかもしれない。  手前味噌ですが STUArray を用いたエラトステネス の篩実装例 http://d.hatena.ne.jp/g940425/20110827/1314442246  後半で unsafe な関数使ってるけど……こういう使い かたなら問題ないはず。    
  • 35. Agenda  とりあえず普通の配列( Array, UArray )  O(1) で変更できなきゃ配列じゃない! (IOArray,  IOUArray)  型クラスの話をしよう (IArray, MArray)    
  • 36. 抽象化したい!  Uarray と Array 、 IOUArray と IOArray は Box 化 / 非 Box 化の違いでしかない。 → これらの違いを無視した一般的なインタ フェースの関数を書きたい。    
  • 37. 例えば……  sumA : Int 要素を持つ Array の全要素の和    
  • 38. 例えば……  sumA をそのまま UArray に使おうとすると、当然の ように型エラーで怒られる。    
  • 39. 例えば……  型ごとに関数を用意すればお k !    
  • 40. いやいや <「お k !」じゃねえ! 関数本体全く同じじゃねえか!  こういう、形がほとんど同じ関数を連ねるのを 「ボイラープレート( boilerplate )」という → Haskeller が最も嫌うもの 私も大嫌いです  形が同じなら、まとめてかけるはず    
  • 41. IArray/MArray  変更不可配列 (Immutable Array) を抽象する型ク ラスが IArray  可変配列 (Mutable Array) を抽象する型クラスが MArray  型クラス: Iarray/MArray はそれ自体「型」ではな い( IArray  〜 型の値、は存在しない)    
  • 42. IArray/MArray  IArray a e : e 型の要素を持つ変更不可配列 a  Marray a e m : m モナドのコンテクストで変更可能 な、 e 型の要素を持つ配列 a    
  • 43. 例ふたたび  IArray を使って sumA を抽象化  GHC 拡張 FlexibleContexts が必要(理由は後述)    
  • 44. 例ふたたび  sumA :: IArray a Int => … → 「 a が Int を要素に持つ変更不可配列」 という型制約をかけている  sumA :: … => a Int Int → Int → a Int Int → Int という型を持つので、 a として当てはまる(= IArray a Int というインス タンスが宣言されている)もの全てに適用可能    
  • 45. 例ふたたび  Data.Array.IArray にインスタンスとして  IArray Array e  IArray UArray Int が宣言されている。( e は型変数で、なんでもよい) → a が Array でも UArray でも型が合う!  IArray Array Int => Array Int Int → Int OK !:インスタンス IArray Array e がある  IArray UArray Int => UArray Int Int → Int OK !:インスタンス IArray UArray Int がある    
  • 46. 補足( 3 )  Q. かける制約は IArray a e でよかったのでは?  A. 実は、 IArray UArray e という要素の型 e につい て一般化されたインスタンスはない。 (Data.Array.UArray のドキュメント参照) → なのでこの例では IArray a Int のインスタンス 宣言を使っており、そのため制約が IArray a Int となっている。  IArray Array e => Array Int Int → Int OK !:インスタンス IArray Array e がある  IArray UArray e => UArray Int Int → Int NG !:インスタンス IArray UArray e はない    
  • 47. 補足( 4 )  本来、型制約に具体的な型を入れることはできな い。 → IArray a Int という制約はふつう許されない。  これを許可するのが FlexibleContexts 拡張    
  • 48. 総括  Haskell でも普通に配列使える  しかし、速いプログラムにするには工夫が要る →使えるところでは Unboxed な配列を使う、とか  抽象化もできる → ただ、なんでもかんでも抽象化すればいいと いうわけじゃない。 Lazy/Strict が本質的な場面 もあるだろうし、競技プログラミングでわざわざ 使いもしないのに抽象化した関数を書くのは得 策でない。    
  • 49. より進んだ話題  Haskell には並列処理用行列 repa ( [hackage]http://hackage.haskell.org/packa ge/repa )という、データ並列プログラミングをサ ポートする特殊なライブラリもある。 → 配列というよりは「行列」 → ついこの間( 2012/9/24 ) GHC­7.6 にも対応  DiffArray なんてのもある( Immutable かつ更新も それなりに速い) [hackage]http://hackage.haskell.org/package/diffa rray