map関数
初めに
- Haskellを学ぶ際には、map関数を避けて通れない、ということで、自分自身混乱しかけたので、まとめてみました。
- 値と関数を同一視すること。そのためには、型を意識することが大事です。
準備
変数に束縛するのが定数であるか関数であるかにかかわらず、「変数 = 値」という一貫した形でも定義できる。
ということで、関数も値も変数と呼ぶ。
関数型
--関数も変数
--[引数の型(型変数)] -> [返り値の型]の形。
--1変数引数の場合
Prelude> :t fst
fst :: (a, b) -> a
{-
↑ ↑
変数 型変数
-}
Prelude> :t head
head :: [a] -> a
{-
↑ ↑
変数 型変数
-}
Prelude> :t tail
tail :: [a] -> [a]
{-
↑ ↑
変数 型変数
-}
- 1変数関数の場合
Prelude> head [1,2,3,4]
1
Prelude> :t head
head :: [a] -> a
head関数を2つの側面から見る。(なので、関数も変数は見方が違うだけのもの)
1. リストを引数にとり、戻り値を返す関数
2. [a] -> a型の変数
多変数関数の場合を考えると、より鮮明になる。
- 2変数関数の場合
--takeはIntとリストを引数に取り、先頭から指定された数の要素を取り出すリストを返す2変数関数。
Prelude> take 1 [1,2,3]
[1]
Prelude> :t take
take :: Int -> [a] -> [a]
{-
take :: Int -> ([a] -> [a]) のこと。
-}
take関数を3つの側面から見ることができる。(なので、関数も変数は見方が違うだけのもの)
1. Int と [a]を引数にとり、[a]を返す関数
2. Intを引数に取り、[a] -> [a]型の変数を返す関数
3. Int -> [a] -> [a]型の変数
2番目のとらえ方を採用すると、(take 1)関数は、
1. [a]を引数に取り、[a]を返す関数
2. [a] -> [a] 型の変数
の二つの見方ができる。
Prelude> :t take 1
take 1 :: [a] -> [a]
map関数について
定義
map関数は、1変数関数と、リストを受け取り、その関数をリスト内のすべての要素に対して、作用させて、新規のリストを戻り値とする。
例 1
Prelude> map (+2) [2,3,4,5,6]
[4,5,6,7,8]
というような使用例。
C#を知っているなら、LINQのSelect関数と同じ。
using System.Linq;
public class Hello{
public static void Main(){
//SelectがHaskellのmap関数に相当。
Enumerable.Range(2,5).Select(c =>c + 2)
.ToList()
.ForEach(c=>System.Console.WriteLine(c));
}
}
もしくは、pythonのリスト内包表記と同等。
print ([x+2 for x in range(2,7)])
説明
Prelude> :t map
map :: (a -> b) -> [a] -> [b]
map関数は3つの側面から見ることができる。
1. (a -> b)型の変数 と [a]を引数にとり、[b]を返す関数
2. (a -> b)型の変数を引数に取り、[a] -> [b]型の変数を返す関数
3. (a -> b) -> [a] -> [b]型の変数
2番目の考え方が特に大事。
冗長だが、2項演算子(+)を用いるので、同様の説明をする。
Prelude> :t (+)
(+) :: Num a => a -> a -> a
+演算子は3つの側面から見ることができる。
1. a と aを引数にとり、aを返す関数
2. a型の変数を引数に取り、 a -> a型の変数を返す関数
3. a -> a -> a型の変数
2番目の観点で+演算子を見ると、
Prelude> :t (+2)
(+2) :: Num a => a -> a
ということで、(+2)をa -> a型の変数とみることができる。
map関数の挙動としては、
Prelude> map (+2) [2,3,4,5,6]
[4,5,6,7,8]
-- a -> a型の変数である(+2)とリストを引数に取っている
-- !! 1 は、リストから、1番目の要素を取ってくる演算子
Prelude> map (+2) [2,3,4,5,6] !! 1
5
map関数の応用
第一引数に、2変数関数を持ってきたときはどうなるのか?
f :: a -> b -> c
これは、a を引数として、b -> c型の変数を返す関数とみることもできる。
map (+) [2,3,4,5,6] !! 1は、(+3)、つまり、1変数関数となる。
(+3)自体はコンソールにprintできないので、以下を実行してみる。
-- map (+) [2,3,4,5,6] !! 1は、a -> a型の引数(1変数関数)
Prelude> :t (map (+) [2,3,4,5,6] !! 1)
--map関数にa -> a型の変数(+3) と[a]を引数に取る。
(map (+) [2,3,4,5,6] !! 1) :: Num a => a -> a
Prelude> map (map (+) [2,3,4,5,6] !! 1) [2,3,4,5,6]
[5,6,7,8,9]
ということで、問題ない。
ラムダ式
ラムダ式も、関数と変数を同一視すれば理解は容易い。
-- (\x -> x + 3)は、 a->a型の変数
Prelude> :t \x -> x + 3
\x -> x + 3 :: Num a => a -> a
--map関数にa -> a型の変数(\x -> x + 3) と[a]を引数に取る。
Prelude> map (\x -> x + 3) [2,3,4,5,6]
[5,6,7,8,9]
次やること
次はfmapをまとめたい。
参考
すごいHaskellたのしく学ぼう!
https://www.amazon.co.jp/%E3%81%99%E3%81%94%E3%81%84Haskell%E3%81%9F%E3%81%AE%E3%81%97%E3%81%8F%E5%AD%A6%E3%81%BC%E3%81%86-Miran-Lipova%C4%8Da/dp/4274068854
-
Haskellの内包表記でも書けますが、今回はmap関数を説明するために簡単な例を用いています。 ↩