Haskellのお勉強 その18

第7章「基本的な構文」を読み終わったので、まとめ。

1.コメント

 1行コメント

main = print $ square 5

-- nの2乗
square :: Int -> Int
square num = num * num

 ブロック形式コメント

main = print $ square 5

{-
  nの2乗
-}
square :: Int -> Int
square num = num * num

 リテレイト形式

> main = print $ square 5

  nの2乗

> square :: Int -> Int
> square num = num * num

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

nの2乗

\begin{code}
square :: Int -> Int
square num = num * num
\end{code}

2.レイアウト

 do式による構文

main = do cs <- getContents
          putStr cs

do式によってまとめる方式を、
レイアウトまたはオフサイドルールという。

 ブレスによる構文

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

3.if式

if 条件式 then 式1 else 式2

4.パタンマッチ

 Haskellのパタンマッチは、値のパタンの照合だけでなく、
 変数への束縛も行う。

 パタンの種類

  ・変数パタン
  ・_ パタン
  ・リテラルパタン
  ・タプルパタン
  ・リストパタン
  ・データコンストラクタパタン

 1.変数パタン
>|haskell| 
square :: Int -> Int
 square n = n * n
|

 Haskellのお勉強 17

第6章「基本的な値」を読み終わったのでまとめ。

Haskellには、以下の型が存在する。

 真偽値(Bool値)
 数値  (Int型、Integer型、Float型、Double型)
 文字  (Char型)
 文字列 (String型=[Char]型)
 タプル ((a,b)型)
 ユニット(()型)
 リスト  ([a]型)

参考書では、それぞれの型に対応する関数についても
説明されているけれど、ここでは、省略。

1.真偽値(Bool値)
 真はTrueであり、偽はFalseを取る。それ以外は、真偽いずれでもない。
 
2.数値  (Int型、Integer型、Float型、Double型)
 数値は、Int型、Integer型、Float型、Double型がある。

 Int型は、処理系に依存した符号付整数。
 Integer型は、表現範囲に制限がない整数。
 Float型は、単精度の浮動小数点。
 Double型は、倍制度の浮動小数点。

3.文字  (Char型)
 文字コードとして、Unicodeを用いている。

4.文字列 (String型=[Char]型)
 文字のリスト。

以下のような関係が、成り立つ
 "Haskell" == ['H','a','s','k','e','l'.'l']

5.タプル ((a,b)型)
 リストは、ひとつの型の値のみなのに対して、
 タプルは、複数の型を持つことができる。

6.ユニット(()型)
 空のタプル。

7.リスト  ([a]型)
 同じ型の値を並べたもの。


例題 cat -n の作成

行番号付きの標準出力。

main = do cs <- getContents
          putStr $ numbering cs
         
numbering :: String -> String
numbering cs = unlines $ map format $ zipLineNumber $ lines cs

zipLineNumber :: [String] -> [(Int,String)]
zipLineNumber xs = zip [1..] xs

format :: (Int, String) -> String
format (n,line) = rjust 6 (show n) ++ " " ++ line

rjust :: Int -> String -> String
rjust width s = replicate (width - length s) ' ' ++ s

1.getContentsにて、標準入力から文字列を読み込む。

2.標準入力から読み込んだ文字列に対してLine関数を適用し、
  文字列のリストを生成する。

3.[1..]と文字列のリストに対して、zip関数を適用し、
  [(1,string1),(2,string2),(3,string3)......]のような
  タプルのリストを生成する。

4.3で生成したタプルのリストに対して、format関数を適応する。
  タプルの第一要素である「行番」に対してrjust関数を適応する。

5.rjust関数では、引数として受け取った「6」と「行番」に対して、
  replicate関数を適応する。
  これにより、右詰めの6文字の文字列の生成される。
  replicate 6, 1は" 6"となる

6.format関数では、5で生成された行番文字列と" "と文字列を
  ++関数にて、リストの連結を行う。

7.map関数にて、6で生成された各文字列は、文字列のリストとして
  纏め上げられる。

8.unlines関数で、7の文字列リストをひとつの文字列にする。

9.putStr関数にて、文字列は標準出力に出力される。

Haskell のお勉強 その16

式を計算し値を得ることを「評価」という。
Haskellでは、「遅延評価」という評価方法を用いて、式から値を求める。
「遅延評価」は、ある式を本当に必要になるまで評価しないという評価方式で、
Haskellでは、「最外簡約」と「グラフ簡約」と用いて、実現している。

まず、「簡約」とは、「関数の適応を定義で置き換える」ことで、
これは、仮引数を実引数に置き換えながら、関数の適応を定義に置き換えること。

例えば、

square n = n * n

という値を累乗する関数があるとして

square (1 + 3) = (1 + 3) * (1 + 3)

とする操作のこと。

「最外簡約」とは、引数が(1 + 3)である場合、

square (1 + 3) = (1 + 3) * (1 + 3)

のように、(1 + 3)を計算しないまま、
関数適用することを言う。

JAVAであれば、

square (1 + 3)

となったときに、

square 4 = 4 * 4

というように、まず(1 * 3)を評価してから、
関数に渡される。

これを「最外簡約」に対して「最内簡約」という。

で、つぎに「グラフ簡約」は、

square (1 + 3) = (1 + 3) * (1 + 3)

の場合、(1 + 3) という引数が、定義内で2回計算する
のではなく、(1 + 3)を一度のみ計算することをいう。

Haskell のお勉強 その15

fgrepコマンドの作成
fgrepは、あるパタンを含む文字列を抽出するためのコマンドです。

import System
import List

main = do args <- getArgs
          cs   <- getContents
          putStr $ fgrep (head args) cs
          
fgrep :: String -> String -> String
fgrep pattern cs = unlines $ filter match $ lines cs
    where
        match :: String -> Bool
        match line = any prefixp $ tails line
        
        prefixp :: String -> Bool
        prefixp line = pattern `isPrefixOf` line

以上のソースでは、まずgetArgsアクションを使用するためにSystemモジュールを、
prefixp関数とtails関数を使用するためにListモジュールをインポートしている。

mainアクションの1行目で、引数を読み込み、
2行目で標準入力を読み込んでいます。

3行目は、まず始めに読み込んだ引数に対してhead関数を適用して、
複数入力された引数の中の一番はじめのものを取得します。

次に、(head args)の結果と標準入力で読み込んだ文字列に対して
fgrep関数を適用。

fgrep関数を適用する第一引数は、文字列を検索するためのパタンとなり、
第二引数の文字列は、調査用の文字列です。

fgrep関数では、第二引数で渡された文字列に対してline関数を適用します。
line関数は、grefによると以下のように説明されています。

Prelude.lines
lines :: String -> [String]
lines str

文字列 str を行ごとに分割しそのリストを返す。
行は \n または文字列末尾で終端するとする。

全てのシステムにおいて行末コードは \n であり、
\r\n および \r は行末とは見なされない。

see also: unlines, words

lines "a\nb\nc\n" = ["a", "b", "c"]
lines "a\nb\nc" = ["a", "b", "c"]
lines "a\nb\r\nc" = ["a", "b\r", "c"]
lines "" = []
lines "\n" = [""]

つぎに、filter関数(高次関数)をmatch関数と(line cs)の結果に対して
適応します。

filter関数は、高次関数であり、第二引数で渡された文字列に対してmatch関数を
適用し、結果としてTrueが返された時の文字列を返します。

match関数では、渡された文字列に対して、tails関数を適用します。
tails関数は、hrefによると以下のような動作をします。

Data.List.tails
tails :: [a] -> a
tails xs

リスト xs のインデックス 0 以降、1 以降、2 以降……のリストを返す。

see also: inits, tail

tails [1,2,3] = [[1,2,3],[2,3],[3],]
tails
= [[]]

any関数(これも高次関数)は、第一引数で渡された文字列のリストすべてに対して
第一引数で渡された関数を適用します。結果にTrueが一つでも含まれていれば
Trueを返し、なければFalseを返します。

prefixp関数では、各要素に対して、isPrefixOf関数を用いて
パタンが含まれているかを判定します。

Haskell のお勉強 その14

echoコマンドの作成

import System

main = do args <- getArgs
          putStrLn $ unwords args

今回出てきた新しい要素は、
import宣言、getArgsアクション、unwords関数の3つです。

まず、import宣言についてです。
これは、関数や変数を定義しているモジュールを使用する時に
用いる宣言です。

今回の場合、importで呼び出されているモジュールは、
Systemモジュールです。

ちなみに、main変数が属しているモジュールは、mainモジュール。
getContentsアクションなどは、Preludeモジュールに定義されている。

次に、getArgsアクションについてです。
これはプログラムの後に続く引数をプログラムの中に
読み込むためのアクションです。

最後に、unwords関数についてです。
これは、プログラムの後に続く引数のリストを空白をはさんで
結合した文字列とするものです。

Haskell のお勉強 その13

map関数は、第2引数として渡されたリストの各要素に対して
第1引数で渡された関数を適応し、その結果をリストにまとめてます。

今回、注目するのは、
「第2引数として渡されたリストの各要素に対して
第1引数で渡された関数を適応」という部分。

リストの各要素に対してなんらかの処理を行うという場合、
C言語などでは、for文やwhile文と言った繰り返しのための構文を
使います。

では、Haskellでもそうなのかというと、そうではない模様。
Haskellには、for文やwhile文のような繰り返しのための構文が
用意されていないのです。

このような状況で、そのようにして繰り返しを実行しているのかというと
再帰」を使用して処理を行っています。

再帰」というのは、ごく簡単にいうと、ある関数の中で同じ関数が
呼び出されているような処理を言います。

では、map関数の定義を見てみることにします。

map :: (a->b) -> [a] -> [b]
map f []     = []
map f (x:xs) = f x : map f xs

関数が2つ定義されています。
1つ目の定義は、fが関数にマッチし、(空リスト)がマッチすれば、実行されます。
戻り値は、
です。

次に2つ目の定義についてです。
問題は、こちら側ですね。

map f (x:xs) = f x : map f xs

f は、先ほども言ったとおり、関数にマッチします。
(x:xs)は、リストが渡された時、一番はじめにある値をxに代入し、
残りをxsにリストとして格納されます。

ここで、「:」の機能についてですが、「:」は、リストを生成する演算子
パタンマッチとして用いる時は。リストを分解する機能を有し、
定義部分で用いた時は、値をリストに追加するという機能を有します。

f x は、xに格納された値がf関数に適応されます。
map f xsは、残った値のリストxsとf関数を再度map関数に適応させます。

具体的に値を用いてみるとこんな感じ

仮りに[1,2,3]というリストが渡されるとすると
map f (x:xs)は次のようになります。

1. map f (1:[2,3]) = f 1 : map f ([2,3])
2. map f (2:[3]) = f 2 : map f ([3])
3. map f (3:[ ]) = f 3 : map f ([ ])

で最後に[]になってしますので、

4. map f [ ] = [ ]

が適用されます。で

5. map f (3:[ ]) = f 3 : [ ]
6. map f (2:[3]) = f 2 : [f 3]
7. map f (1:[2,3]) = f 1 : [f 2,f 3]
8. [f 1, f 2, f 3]

という結果が返る。

参考書

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

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

Haskell のお勉強 その12

パターンマッチを用いたexpandコマンドの作成。

まずは、ソースの作成

tabStop = 8
main = do cs <- getContents
          putStr $ expand cs
          
expand :: String -> String
expand cs = concatMap expandTab cs

expandTab :: Char -> String
expandTab '\t' = replicate tabStop ' '
expandTab  c   = [c]

今回のソースで目新しいものは、
concatMap関数とexpandTab関数、replicate関数の3つの関数。

concatMap関数は、concat関数とmap関数が合わさったもの。
concat関数とmap関数に関しては、前々回、前回で学んだので省略。

次にexpandTab関数が二つ定義されていることに注目。
引数を見てみると、片方が「'\t'」で、もう片方が「c」となっている。

参考書を読むと、どうやら、Haskellの関数には変数の他に
パターンとなる文字列を引数にすることが可能らしい。

パターンが複数あれば、その分だけ関数の定義が増える。
パターンにマッチしないものは、実行時エラーになる。

replicate関数は、第2引数で渡された文字を
第一引数で渡された数値分並べたリストを返す関数。