SE情報技術研究会’s blog

http://se-info-tech.connpass.com

2015-12-29 『ふつうのHaskellプログラミング』 もくもく読書会(第7章) の振り返り

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

  • 作者: 青木峰郎,山下伸夫
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2014/10/05
  • メディア: オンデマンド (ペーパーバック)
  • この商品を含むブログを見る

第7章 基本的な構文

コメント

1行コメント

  • -- コメント
  • -演算子にも使えるので--の後に記号を入れるとコメントとして解釈されない
  • 通常は--の後の空白を入れる
square n = n * n -- nの2乗

ブロック形式のコメント

  • {-- 複数行のコメント --}
  • コメントのネストも可能
{--
関数をコメントアウト
square n = n * n {-- nの2乗 --}
--}

リテレイト形式

  • リテレイト形式とはコメントの中にコードを描く形式
  • 慣例的に拡張子は「.lhs」※ 「.hs」だとエラーになる
  • 先頭に「>」をつけた行だけがコードになる
  • \begin{code}から\end{code}で囲む形式もある
  • 「>」か\begin{code}から\end{code}のどちらかしかダメなもよう

下記はコンパイルは通る

> main = print $ square 5

関数 square は数値の2乗をあらわす

> square :: Int -> Int
> square n = (n * n)

下記のコードはコンパイルエラーを起こす

\begin{code}
main = print $ square 5
\end{code}

関数 square は数値の2乗をあらわす

> square :: Int -> Int
> square n = (n * n)

実行すると…

>ghc literateNG.lhs
[1 of 1] Compiling Main             ( literateNG.lhs, literateNG.o )

literateNG.lhs:8:12: parse error on input ‘=’

レイアウト

ブレース構文

  • ;で式を区切り{}で囲むことでコードブロックを表現できる

例えば、インデントを使ったdo式を

main = do cs <- getContents
          putStr cs

下記に書き換えることが可能

main = do 
{
    cs <- getContents;
putStr cs;
}

レイアウト

  • 複数の式の桁をそろえることで式をまとめる方式をレイアウトもしくはオフサイドルールという
  • 束ねたい式のインデントをそろえると、それらが1つのコードブロックに属すると解釈される
  • ただし行頭には書けない ※ 最低1文字の空白が必要

do式は下記のように書ける

main = do
  cs <- getContents
  putStr cs
  • インデントより右側なら前の行の続きとみなされる
main = do 
  cs <- 
    getContents
  putStr cs

そのため式の途中でインデントを揃えると別の式とみなされ粉ピルエラーになるもよう

main = do 
  cs <- 
  getContents
  putStr cs

※ 最低でも2行はインデント

コメント
  • 自動でインデントするエディタなら改行したほうがインデントを揃えやすい

    if式

  • 式1と式2は式でありコードブロックでないことに注意
if 条件式 then1 else2

パターンマッチ

  • 値のパターンの場合分けのこと
  • パターン分けと同時に値の一部の変数を束縛することもできる
  • 関数定義などで使われる
  • すべてのパターンマッチに失敗したエラー
  • ghc-Wのオプションをつけると実行時エラーが出る場合は警告が出る
  • パターンには具体的な値、タプル、リスト、データコンストラクタが使える
コメント

@(アズパターン:as-pattern)

  • パターンで使っている変数名とは別にパターン自体に変数名をつける方法
変数名(パターン)

例:

str@(c:cs)

とある場合、値に[1,2,3]だと

  • str = [1,2,3]
  • c = 1
  • cs = [2, 3]

ガード

  • 変数の値の条件によって処理する定義を分ける方法
  • ガードは|=の間のことをあらわす
  • 条件は複数設定でき、上から順に判定される
  • 最後に「otherwise」で全ての条件にあわない場合の処理を行うことが可能
関数名 変数 | 条件1 = 定義1
            | 条件2 = 定義2
            || otherwise = 定義…
  • パターンマッチと合わせて使うことも可能

case式

  • 関数の引数以外でパターンマッチやガードを使う方法
  • パターンやマッチに合った場合に実行される処理の定義は=ではなく->の右に記述する
  • case式のogの後に{}で囲う記述も可能
caseof 
  パターンA
    | ガード1 -> 定義A1
    | ガード2 -> 定義A2
  パターンB
    | ガード1 -> 定義B1
    | ガード2 -> 定義B2
  …

関数定義

  • 関数は引数を使って何かする変数とみなすこと可能(関数束縛)

関数や変数の識別子

使える文字

  • アルファベット(a~z、A~Z)
  • 数字(0~9)
  • _
  • '

規約

  • 1文字目は小文字か_ ※ ただし公式に_は避けるようにかいてある
  • 'は補助的な関数につけられることが多い
  • 予約語もあり、それは使えない

二項演算子

  • 引数と引数の間に記号を書くことで表現できる関数
  • 使える記号がいくつか用意され、独自に定義できる
  • 予約語もあり、それは使えない
  • 演算子をむやみに作るとコードが読みづらくなるので使う前に検討すべき
  • 識別子を使った通常の2引数とる関数も「`」で囲うことで二項演算子として使用できる
  • 逆に()で囲って二項演算子を前置形式で記述することも可能

優先順位と結合性

  • 優先順位には0~9があり、0が最低、9が最高
  • 左結合:1+1+1とある場合、(1+1)+1と左側の式から結合していく演算子
  • 右結合:x=y=1とある場合、x=(y=1)と右側の式から結合していく演算子
  • 非結合:x>y>zと結合することができない演算子
  • 独自で定義した演算子にも優先順位と結合性を定義できる

独自で定義した演算子の優先順位と結合性の設定

結合性 優先順位 演算子

結合性には下記がある

  • infixl:左結合
  • infixr:右結合
  • infixx:非結合

優先順位は省略可能(その場合デフォルトで9)

let式

  • その中でだけ有効な変数の定義(束縛の定義)方法
  • 式なのでそれ自体も値を持つ
  • 変数名が重複した場合、関数とlet式ならlet式の変数、ネストしたlet式ならネストした変数と、内側の変数が外側の変数を覆い隠す(シャドウイング
  • パターンを使って束縛できる
  • よく新しい変数を導入するために使われる

例:

f n = let x = n + 1
          y = n + 2
          z = n + 3
      in x * y * z

where節

  • 特定の定義内でだけ有効な変数の定義(束縛の定義)方法
  • 節なので値を持たない
  • 変数がガードにまたがって使える
  • 変数がパターンにまたがって使えない
  • パターンを使って束縛できる
  • よく関数内で使うための関数を定義するのに使われる

expandTab width cs = concatMap translate cs
  where
    translate '\t' = replicate width ' '
    translate c = [c]

練習問題

  • 特に問題なし