プログラミング勉強ログ

プログラミングの勉強記録

Yet Another Haskell 4章 (2)

4.2 Polymorphic Types
多相型

関数tailはリストを引数にとるが、そのリストが内包する値の型に関わらず適用できる。

Prelude> tail [5,6,7,8,9]
[6,7,8,9]
Prelude> tail "hello"
"ello"
Prelude> tail ["the","man","is","happy"]
["man","is","happy"]

これはtailの型が多相型[a]->[a]だからだ。

Prelude> :t tail
tail :: [a] -> [a]

つまり、tailは任意のリストを引数として受け取り、同じ型のリストを返す。


fstの型を調べてやると、

Prelude> :t fst
fst :: (a, b) -> a

つまり、fstは任意のペアを引数として受け取り、ペアの最初の要素と同じ型の値を返す。


この多相型ってのは、C++のテンプレートや、JavaC#ジェネリクスと似たようなものなのかな。

Exercise 4.2 省略

Yet Another Haskell 4章 (1)

4.1 Simple Types

Haskellは静的型付け言語であり、全ての式は型を持っている。
さらに、タイプインターフェイスというシステムがあり、式の型を指定する必要がない。型はコンテキストから推論される。


HugsGHCでは、:tコマンドを使用することで式の型を調べることができる。

Prelude> :t 'c'
'c' :: Char

式'c'の型はCharだ。


Int(整数型), Double(浮動小数点型), Char(文字型), String(文字列型)等など、たくさんの組み込み型がある。

文字列の型は、

Prelude> :t "Hello"
"Hello" :: [Char]

原文だと、Stringになっているけど、まあ同じものだし。


比較の結果はBool

Prelude> :t 'a' == 'b'
'a' == 'b' :: Bool


違う型同士を比較しようとすると、エラーになる。

Prelude> :t 'a' == "b"

<interactive>:1:8:
    Couldn't match expected type `Char' with actual type `[Char]'
    In the second argument of `(==)', namely `"b"'
    In the expression: 'a' == "b"


::を使って明示的に型を指定することもできる。

Prelude> :t "b"::String
"b"::String :: String


上の例では"a"の型はStringの可能性しかないため特に意味は無いが、数値の場合は少し状況が異なる。

Prelude> :t 5::Int
5::Int :: Int
Prelude> :t 5::Double
5::Double :: Double

5はIntともDoubleとも解釈できる。

もし、型を指定しなければどうなるか?

Prelude> :t 5
5 :: Num a => a

変な結果になった。


5の型aはNumクラスのインスタンスという意味らしい。
詳しくは4.3で扱う。

Exercise 4.1

1. 'h':'e':'l':'l':'o':[] :: [Char]
2. [5, 'a'] -> error
3. (5,'a') :: Num t => (t, Char)
4. (5::nt) + 10 :: Int
5. (5::Int) + (10::Double) -> error

Yet Another Haskell 3章 (8)

3.8 Interactivity
ユーザーインタラクションに関して


キーボードからの入力を受け付ける関数(scanf, cin等)
は厳密な意味では関数ではない。
ユーザーの入力によって結果が変わってしまうからだ。
この問題は数学の圏論におけるモナドによって解決された。
モナドに関しては、5章や9章で詳しく扱う。

インタラクティブなプログラムを書くためには、doを使う。
これを使うことで、プログラムの実行順序を制御できる。
Haskellはlazyな言語なので、通常、プログラムの実行順序は決まっていない)

ユーザーの名前を聞く簡単なプログラムが次だ。

module Main
    where

import System.IO

main = do
  hSetBuffering stdin LineBuffering
  putStrLn "Please enter your name: "
  name <- getLine
  putStrLn ("Hello, " ++ name ++ ", how are you?")

これを実行すると次のようになる。

*Main> main
Please enter your name:
quotientK
Hello, quotientK, how are you?

確かに、インタラクションができている。


次にプログラムの解説。

import System.IOは入出力のための関数を定義しているIOというライブラリを取り込む。

doはコマンドを順番に実行するためのもの。

hSetBuffering stdin LineBufferingはGHCでの実行に必要なもので、特に重要ではないっぽい。(GHCでは通常ある一定の長さの入力を行うまで、処理が次に行かないらしい。このコマンドを実行すると処理が次に行くようになるらしいが、試しにこの行を削除して実行してみても特に問題無かった。環境がWindowsだからかな。)

putStrLnは文字列を画面に出力するコマンド。

name <- getLineは、他の言語では、name = getLine()のようなもの。
ただ、getLineは関数ではない(ユーザーの入力によって結果が代わる)ため、<-を使う。これは「getLineを実行し、その結果をnameに保存する」を意味する。

最後の行は、nameを加工して、画面に文字列を出力する。


こういうものだと思えば何のことは無いが、この後ろで働いてるモナドの理解が大変なんだろうね。まあとりあえず今は気にしないでおこう。


お次は、数字当てゲームの例。
1から100までの数字をランダムに決め、それを当てるプログラムだ。
乱数もまた純粋な関数ではないため、モナドを使う必要がある。

module Main
    where

import System.IO
import System.Random

main = do
  hSetBuffering stdin LineBuffering
  num <- randomRIO (1::Int, 100)
  putStrLn "I’m thinking of a number between 1 and 100"
  doGuessing num

doGuessing num = do
  putStrLn "Enter your guess:"
  guess <- getLine
  let guessNum = read guess
  if guessNum < num
    then do putStrLn "Too low!"
            doGuessing num
    else if guessNum > num
           then do putStrLn "Too high!"
                   doGuessing num
           else do putStrLn "You Win!"


num <- randomRIO (1::Int, 100)は、1から100までの乱数を生成し、numに代入する。getLineと同じく<-を使用する。

Intは生成する乱数が整数であること指定するためのもの。100の方にはいらないのは推論されるからだろう。

その次がdoGuessingを実行する。

doGuessing num = doでdoGuessingの定義が始まる。

guess <- getLineで、guessにユーザーからの入力を保存する。

let guessNum = read guessで、guessを数値に変換する。(getLineの結果はStringだから)
readは純粋な関数のため、read guessでは<-は使わない。
なお、doの中ではlet使用時にinは必要ないとのこと。

そして、guessとnumの値によって分岐し、次に続く処理を決める。

なるほど。


次は、再帰的にコマンドを実行する際の注意。
まず間違った例から。

askForWords = do
  putStrLn "Please enter a word:"
  word <- getLine
  if word == ""
    then return []
    else return (word : askForWords)

間違っているのは最後の行。
askForWordsはリストではなく。アクション(action)だから、:で繋ぐことができない。

で、正しい例は、

askForWords = do
  putStrLn "Please enter a word:"
  word <- getLine
  if word == ""
    then return []
    else do
      rest <- askForWords
      return (word : rest)

<-を使ってアクションの結果をrestに代入し、それを:で繋いで返すのが正しい。

returnがしれっと出てきているが、この<-やreturnがモナドの一部(?)というのは聞いたことがある。

基本的な部分は理解できた。と思う。

Exercise 3.10

module Main
    where

import System.IO

main = do
    hSetBuffering stdin LineBuffering
    nums <- scanNumbers
    putStrLn ("The sum is " ++ show (Main.sum nums))
    putStrLn ("The product is " ++ show (Main.product nums))
    printFactorialList nums

scanNumbers = do
  putStrLn "Give me a number (or 0 to stop):"
  line <- getLine
  let n = read line
  if n == 0
    then return []
    else do
      rest <- scanNumbers
      return (n : rest)

sum list = foldl (+) 0 list

product list = foldl (*) 1 list

factorial 1 = 1
factorial n = n * factorial(n - 1)

printFactorialList [] = do
  return ()

printFactorialList (x:xs) = do
  putStrLn ((show x) ++ " factorial is " ++ (show (factorial x)))
  printFactorialList xs

実行結果

*Main> main
Give me a number (or 0 to stop):
10
Give me a number (or 0 to stop):
2
Give me a number (or 0 to stop):
5
Give me a number (or 0 to stop):
1
Give me a number (or 0 to stop):
0
The sum is 18
The product is 100
10 factorial is 3628800
2 factorial is 2
5 factorial is 120

何もしないことの表し方でハマった…。return ()か。

Yet Another Haskell 3章 (7)

3.7 Recursion
再帰に関して

C言語などでは、for文を使ったループをよく使う。
しかし、for文では破壊的変更を伴うため、Haskellでは使われない。

代わりに再帰を使う。

例えば、C言語で階乗を計算する関数は次のように書ける。

int factorial(int n) {
  int fact = 1;
  for (int i = 2; i <= n; ++i)
    fact = fact * i;
  return fact;
}

Haskellでは次のように書く。

factorial 1 = 1
factorial n = n * factorial(n - 1)

シンプルですね。

再帰をマスターするのは難しいけど、最初の場合と再帰の場合を分けて考えるのが大事。
例えば、aのb乗を計算する関数は、b=1とそれ以外で分けるといい。

exponent a 1 = a
exponent a b = a * exponent a (b-1)

リストの長さを計算する関数(lengthと同じ関数)は、リストが空の場合と、それ以外で分けるといい。

my_length [] = 0
my_length (x:xs) = 1 + my_length xs

この(x:xs)という記法は特徴的ですね。
リストが空でない場合は、最低1つの要素があるため、先頭の要素をxで操作でき、残りのリストをxsで表し、それを自分自身に渡して再帰すればいい。

filterもリストが空の場合と、それ以外で分けるといい。
リストが空の場合は、空のリストを返し、
リストが空でない場合は、先頭の要素が条件を満たすかしらべ、残りのリストを再帰で処理する。

my_filter p [] = []
my_filter p (x:xs) =
  if p x
    then x : my_filter p xs
    else my_filter p xs

このパターンは頻出しそう。

Exercise 3.7

fib 1 = 1
fib 2 = 1
fib n = fib (n-1) + fib (n-2)

Exercise 3.8

mult a 1 = a
mult a b = a + mult a (b-1)

Exercise 3.9

my_map p [] = []
my_map p (x:xs) = p x : my_map p xs

Yet Another Haskell 3章 (5.2)

3.5.2 Infix
インフィックスに関して

(+), (*), (++)等の記号の関数は、インフィックス関数と呼ばれる。
これらは()で包むことで、通常の関数と同じように使える。

Prelude> 5 + 10
15
Prelude> (+) 5 10
15

foldlの例で(+)と書いていたのはこれが理由ですね。

通常の関数を``(バッククォート)で包むことで、インフィックス形式で使用できる。

Prelude> map Data.Char.toUpper "Hello World"
"HELLO WORLD"
Prelude> Data.Char.toUpper `map` "Hello World"
"HELLO WORLD"

(何に使うんだろう。)

Yet Another Haskell 3章 (5.1)

3.5.1 Let Bindings

let bindingに関して


今までの知識を使うと、2次方程式の根を求める関数rootsは次のように書ける。

roots a b c =
  ((-b + sqrt(b*b - 4*a*c)) / (2*a),
   (-b - sqrt(b*b - 4*a*c)) / (2*a))


let inを使うことで、関数内でのみ参照できるローカル変数を定義できる。

roots a b c =
  let det = sqrt(b*b - 4*a*c)
  in ((-b + det) / (2*a),
      (-b - det) / (2*a))

ローカル変数は複数定義できる。

roots a b c =
  let det = sqrt(b*b - 4*a*c)
      twice_a = 2*a
  in ((-b + det) / twice_a,
      (-b - det) / twice_a)

letの後は、インデントに注意。

roots a b c =
  let det = sqrt(b*b - 4*a*c)
       twice_a = 2*a
  in ((-b + det) / twice_a,
      (-b - det) / twice_a)

とするとエラーになる。