1 Getting Starged 使ってみよう ぱらぱらめくる『The Haskell Road to Logic, Maths and Programming』

  • この本は、各章がモジュールになっている
  • hugsを使うことを前提として書いてある(このブログはghciを使っている。ずっと続けられるか、どこかでうまくいかなくなるかは、おいおいわかるはず)
  • Exercise 1.3
    • 以下をprime.hsというファイルで保存
divides d n = rem n d == 0
    • 保存したディレクトリで以下、実行してghciを立ち上げ
$ ghci
    • ghci内で色々やってみる
GS> :l prime.hs
[1 of 1] Compiling Main             ( prime.hs, interpreted )
Ok, modules loaded: Main.
Main> divides

<interactive>:63:1:
    No instance for (Show (a0 -> a0 -> Bool))
      arising from a use of ‘print’
    In a stmt of an interactive GHCi command: print it
Main> :t divides
divides :: Integral a => a -> a -> Bool
Main> :i divides
divides :: Integral a => a -> a -> Bool         -- Defined at p
Main> :k divides

<interactive>:1:1: Not in scope: type variable ‘divides’
Main> divides 3 5
False
Main> divides 3 6
True
Main> divides 6 3
False
Main> :r
Ok, modules loaded: Main.
  • 素数の判定アルゴリズム(素数判定)。ある自然数nについて\sqrt{n}までで割り切れなければ、nは素数、というのを実装しているのが以下
    • remは剰余を返す関数、dividesはそれを使って割り切れるかどうかを判定する関数、ldfは再帰的に\sqrt{n}まで試す関数で、ためし割りをする数が大きくなるまで続けはTRUEを返し、そうでなければFALSEを返す。ldはそれの『入口』の関数。それらを使って判定する関数がprime0
module GS -- Getting Starged moduleと言う意味
where -- "where"に続く諸々を条件としたモジュールですということ


{- 
{- 関数の定義は型に関する部分と、実行内容に関する部分からなる-}
{- 型定義には"::"が使われる -}
{- 型定義は省略されることもある-}
{- 実行内容記述では、"="の左側"divides d n"が関数名と引数 -}
{- 右側"rem n d == 0"が引数を使って実行する内容 -}
{- 今回の場合は、"rem n d"という関数に引数n dを渡した結果 -}
{- と0との一致を"==""で確認した結果を返す-}
{- という内容 -}
 -}
divides :: Integer -> Integer -> Bool
divides d n = rem n d == 0 -- d is divisible by n

{-
{- Least divisor -}
{- 1より大きい整数でnを割り切る最小のものを返す -}
{- この値が引数自身であれば、それは素数 -}
-}
ld :: Integer -> Integer
ld n = ldf 2 n

ldf :: Integer -> Integer -> Integer
ldf k n | divides k n = k
        | k^2 > n = n
        | otherwise = ldf (k+1) n -- 再帰的なldfの利用

prime0 :: Integer -> Bool
prime0 n | n < 1 = error "not a positive integer"
         | n == 1 = False
         | otherwise = ld n == n
  • ldは1より大きい最小の約数。map関数を使って、1,...,10の最小約数を確認
GS> map ld [1..10]
[1,2,3,2,5,2,7,2,3,2]
GS> prime0 $ 2^(2^1)+1
True
GS> prime0 $ 2^(2^2)+1
True
GS> prime0 $ 2^(2^3)+1
True
GS> prime0 $ 2^(2^4)+1
True
GS> prime0 $ 2^(2^5)+1
False
GS> prime0 $ 2^(2^6)+1
False
  • Exercise 1.4
    • ldfの">"記号を">="記号に換える。ldfの下流の処理、ld,prime0も換える
ldf' :: Integer -> Integer -> Integer
ldf' k n | divides k n = k
        | k^2 >= n = n -- > を >= に換えた
        | otherwise = ldf' (k+1) n -- 再帰的なldfの利用

-- ldf'対応に換えた
ld' :: Integer -> Integer
ld' n = ldf' 2 n

prime0' :: Integer -> Bool
prime0' n | n < 1 = error "not a positive integer"
         | n == 1 = False
         | otherwise = ld' n == n
    • 結果は、
map prime0 [1..20]
map prime0' [1..20]
    • と変わらない。処理の違いは、k^2 > n と k^2 >= nとの違いであって、それはk^2==nの場合の違いであるが、そのような場合、nはkで割り切れているから、k^2 >(=) nの判定よりも先にdivides k n結果がTRUEとなりその結果が採用されるから
  • Exercise 1.5
    • In the following codes.
  • Exercise 1.6, 1.7
    • divides 自体は、Integerをとって、次にIntegerをとって、Boolを返すもの
    • divides 3 (divides に3を引数として与えた状態)は、もう一つIntegerを取ってBoolを返すもの
    • divides 3 6 (divides に第一引数として3を、第二引数として6を与えた状態)は、Bool値(を持った状態)である
GS> :t divides
divides :: Integer -> Integer -> Bool
GS> :t divides 3
divides 3 :: Integer -> Bool
GS> :t divides 3 6
divides 3 6 :: Bool
  • Identifiers
    • 2種類
    • Variable identifiers
      • 小文字始まり(map,max,fct2list,fctToList,fct_to_list,max')
    • Constructor identifiers
      • タイプを定める。大文字始まり(Bool,Int)
  • 関数はdata-structuresに働きかけるもの、コンストラクタはdata-structureを作り上げるパーツ・ブロック単位
  • 予約語
    • case, class, data, default,deriving, do,else,if,import,in,infix,infixl,infixr,instance,let,module,newtype,of,then,type,where,_ (<-underscore)
  • Exercise 1.9 最小・最大用関数。リストをとって値を返すか、2つの値をとって1つの値を返すか
myMimIntList :: [Int] -> Int
myMimIntList [] = error "empty list"
myMimIntList [x] = x
myMimIntList (x:xs) = min x (myMimIntList xs)

myMimIntTwo :: Int -> Int -> Int
myMimIntTwo x y | x <= y = x
                | otherwise = y

myMaxIntList :: [Int] -> Int
myMaxIntList [] = error "empty list"
myMaxIntList [x] = x
myMaxIntList (x:xs) = max x (myMaxIntList xs)

myMaxIntTwo :: Int -> Int -> Int
myMaxIntTwo x y | x >= y = x
                | otherwise = y
  • Exercise 1.10
    • リストの先頭の値が、指定の値だったら、それを除いたリストを返す(返すリストはカラリストかもしれない。ただし空のリストの先頭はないので、引数のリストが空なら、帰り値は空リストであると指定する。先頭の値が指定の値でなければ、先頭の値と、「残りのリストについて再帰的に値チェックをした出力」を連結したリストを返す
    • 「作業」は値とリストを受け取ってリストを返すことであるし、先頭を取り除く作業と、取り除かれない値は、残りとくっつける作業だけがわかれば、できる作業であるので、その2作業だけが書ければ、後は、実行できるはず、という感じの作り
    • myRemoveFstとして作った関数を、別途、関数内で使うに当たり、条件の与え方としてwhereを使う方法とletを使う方法があるので、それに対応して2つの関数を書いておく
myRemoveFst :: Int -> [Int] -> [Int]
myRemoveFst x [] = []
myRemoveFst x (y:ys) | x==y = ys
                     | otherwise = y: ( myRemoveFst x ys)

mySortIntList :: [Int] -> [Int]
mySortIntList [] = []
mySortIntList xs = m : (mySortIntList (myRemoveFst m xs)) where m = myMimIntList xs

-- 別の書き方、where を使うか let を使うか
mySortIntList' :: [Int] -> [Int]
mySortIntList' [] = []
mySortIntList' xs = let 
                    m = myMimIntList xs
                   in m : (mySortIntList' (myRemoveFst m xs)) where 
  • 整数だけを扱ってきたが、平均を計算したりすると、そうもいかない
    • 数値関係の諸型についてはこちら
-- Intからの変換は何でもfromIntegral
-- Floatへ変換するのは、型定義でFloatになるように指定しているから

myAverage :: [Int] -> Float
myAverage [] = error "empty list"
myAverage xs = fromIntegral (sum xs) / fromIntegral (length xs)

-- Rational型は「整数を分母分子に持つ」と言う型
-- Intは分母が1のRational型の一部とも言える

myAverage' :: [Int] -> Rational
myAverage' [] = error "empty list"
myAverage' xs = fromIntegral (sum xs) / fromIntegral (length xs)
    • 二通りの関数の出力を比較

|