Haskell(하스켈) | basic

하스켈 설치 가 다 되었으면 기본적인 문법부터 알아보겠습니다.

연산

산술 연산자

기본적인 연산은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
ghci> 8 + 20
28
ghci> 26 - 5
21
ghci> 14 * 6
84
ghci> 57 / 5
11.4
ghci> 3 ^ 2
9

여러개의 연산자가 있을 경우에는 연산자 우선순위에 의해서 계산이 됩니다. 또한 괄호를 통해 명시적 우선순위를 표현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ghci> 14 * 6 - 5
79
ghci> 5 - 14 * 6
-79
ghci> 14 * ( 6 - 5 )
14
<음수는 괄호 안에 넣어 함>
ghci> 5 - 14 * (-6)
89
<음수를 괄호 안에 넣지 않을 경우 오류>
ghci> 5 - 14 * -6
<interactive>:15:2: error:
Precedence parsing error
cannot mix ‘*’ [infixl 7] and prefix `-' [infixl 6] in the same infix expression

논리 연산자

하스켈의 부울대수의 표현은 대문자로 시작하여야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
ghci> True && True
True
ghci> True && False
False
ghci> False || True
True
ghci> True || False
True
ghci> not True
False
ghci> not ( False || True )
False

동치 비교 연산자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ghci> 55 == 55
True
ghci> 55 /= 55
False
ghci> "372" == "372"
True
ghci> "372" == "3723"
False
<동일한 타입이 아닐 경우 오류>
ghci> "372" == 372
<interactive>:33:11: error:
• No instance for (Num [Char]) arising from the literal ‘372’
• In the second argument of ‘(==)’, namely ‘372’
In the expression: "372" == 372
In an equation for ‘it’: it = "372" == 372

함수 호출

하스켈에서 모든 것은 함수입니다. + 연산자도 함수입니다.

1
2
ghci> 3 + 2
5

이렇게 대상 중간에 두는 함수를 중위함수(infix function) 이라고 합니다.

중위함수 외에 주로 사용되는 함수의 호출 방식은 전위함수(prefix function) 입니다. 전위함수는 함수이름 공백 매개변수 의 형태로 호출 됩니다.

1
2
3
ghci> succ 3
4
*succ 함수는 매개변수를 받아서 증가된 값을 반환.

여려개의 매개변수를 받는 함수를 호출하는 방식입니다.

1
2
3
4
ghci> max 3 9
9
ghci> min 3 9
3

함수가 연산자 보다 우선 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ghci> 3 + min 3 9
6
ghci> max 3 9 + 3
12
ghci> (max 3 9) + 3
12
<괄호 안에 넣은 경우>
ghci> succ 3 * 6
24
ghci> succ (3 * 6)
19
<매개변수의 순서에 따라 혼란이 올 수 있는 경우의 표현>
ghci> div 4 2
2
ghci> 4 `div` 2
2

함수 정의

ex.hs 파일을 만들고 다음과 같은 함수를 정의합니다.

1
double a = a + a

REPL 에서 함수를 정의하고 사용합니다.

1
2
3
4
5
ghci>:l ex
[1 of 1] Compiling Main ( ex.hs, interpreted )
Ok, modules loaded: Main.
ghci> double 4
8

ex.hs 에 다음 함수를 추가합니다.

1
2
add a b = a + b
doubledouble a b = double a + double b

다시 정의하고 사용합니다.

1
2
3
4
5
6
7
ghci>:l ex
[1 of 1] Compiling Main ( ex.hs, interpreted )
Ok, modules loaded: Main.
ghci>add 3 5
8
ghci>doubledouble 3 5
16

다음은 함수내에서 if 를 사용해 보겠습니다. 다음 함수를 추가합니다.

1
2
3
time a = if a > 12
then a
else a - 12

다음 함수는 24시간 표현의 시간 입력하면 12시 표현으로 바꿔주는 함수입니다.

하스켈에서의 if 는 단독으로 사용할 수 없고 if-then-else 표현의 꼬리 순환(tail recursion)이라고 하는 표현식 입니다.

리스트(list)

리스트는 같은 타입의 묶음입니다. [] 로 감싸져 있습니다.

1
2
3
4
5
6
7
8
9
ghci> let randomNum = [2,9,8,7,3,4]
ghci> randomNum
[2,9,8,7,3,4]
<타입 체크>
ghci> :t randomNum
randomNum :: Num t => [t]
* let 키워드는 함수를 정의하는 것을 의미 => 스크립트에 정의하고 :l 로드하는 것과 같은 의미

++ : 를 이용하여 리스트를 연결할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ghci> [2,4,8,3,1] ++ [34,52,4,9]
[2,4,8,3,1,34,52,4,9]
ghci> (++) [2,4,8,3,1] [34,52,4,9]
[2,4,8,3,1,34,52,4,9]
ghci> "372" ++ " " ++ "world"
"372 world"
* 문자열은 문자들의 리스트 이므로 가능
<단일항목과 리스트와의 연결은 : 를 사용>
ghci> 3:[5,7,20]
[3,5,7,20]
ghci> (:) 3 [5,7,20]
[3,5,7,20]
ghci> 3:10:104:[5,7,20]
[3,10,104,5,7,20]

리스트의 한 항목을 조회하려면 !! 를 사용합니다.

1
2
3
4
5
6
7
ghci> [3,10,389,34,5,77] !! 2
389
ghci> (!!) [3,10,389,34,5,77] 2
389
ghci> "372 world" !! 8
'd'

리스트 끼리 비교연산을 할 수 있습니다. 비교할 경우 한목 순서대로 비교하여 값이 같은 항목은 넘어가고 값이 다른 항목이 나오면 비교연산을 수행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ghci> [1,2,3] == [1,2,3]
True
ghci> (==) [1,2,3] [1,2,3]
True
ghci> [2,2,3] == [1,2,3]
False
ghci> [2,2,3] > [1,2,3]
True
ghci> [2,2,3] >= [1,2,3]
True
ghci> [1,2,3] >= [1,2,3]
True
ghci> [1,2,3] == [1,2]
False
ghci> [1,2,3] >= [1,2]
True
ghci> [1,2,3] > [1,2]
True

리스트 안에 같은 타입의 리스트를 항목으로 둘 수 있습니다.

1
2
3
4
5
6
ghci> [[1,2,3], [4,5,6], [7,8,9]]
[[1,2,3],[4,5,6],[7,8,9]]
ghci> list ++ [[11,22,33]]
[[1,2,3],[4,5,6],[7,8,9],[11,22,33]]
ghci> [44,55,66] : list
[[44,55,66],[1,2,3],[4,5,6],[7,8,9]]

추가로 리스트에 관련된 함수들이 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
ghci> head [0,1,2,3,4,5,6,7,8,9,10] => 가장 첫 항목 하나를 반환
0
ghci> last [0,1,2,3,4,5,6,7,8,9,10] => 가장 마지막 항목 하나를 반환
10
ghci> tail [0,1,2,3,4,5,6,7,8,9,10] => 가장 첫 항목을 빼고 나머지를 반환
[1,2,3,4,5,6,7,8,9,10]환
ghci> init [0,1,2,3,4,5,6,7,8,9,10] => 가장 마지막 항목을 빼고 나머지를 반환
[0,1,2,3,4,5,6,7,8,9]
ghci> length [0,1,2,3,4,5,6,7,8,9,10] => 항목의 총 갯수를 반환
11
ghci> reverse [0,1,2,3,4,5,6,7,8,9,10] => 역순으로 바꾸고 반환
[10,9,8,7,6,5,4,3,2,1,0]
ghci> null [0,1,2,3,4,5,6,7,8,9,10] => 리스트의 항목의 유무 확인
False
ghci> take 5 [0,1,2,3,4,5,6,7,8,9,10] => 리스트 첫 항목부터 5(매개변수) 까지의 항목을 반환
[0,1,2,3,4]
ghci> drop 5 [0,1,2,3,4,5,6,7,8,9,10] => 리스트 첫 항목부터 5(매개변수) 까지의 항목을 버리고 반환
[5,6,7,8,9,10]
ghci> maximum [0,1,2,3,4,5,6,7,8,9,10] => 리스트 중 가장 큰 수 반환
10
ghci> minimum [0,1,2,3,4,5,6,7,8,9,10] => 리스트 중 가장 작은 수 반환
0
ghci> sum [1,2,3,4,5,6,7,8,9,10] => 리스트 전체 수를 더한 값 반환
55
ghci> product [1,2,3,4,5,6,7,8,9,10] => 리스트 전체 수를 곱한 값 반환
3628800
ghci> elem 4 [0,1,2,3,4,5,6,7,8,9,10] => 리스트 전체 항목 중 4(매개변수) 유무 확인
True
ghci> 4 `elem` [0,1,2,3,4,5,6,7,8,9,10] => 중위함수로 표현하면 가독성이 더 높을 수 있음
True
<함수에 함수를 적용>
ghci> take 15 (cycle [3,7,2])
[3,7,2,3,7,2,3,7,2,3,7,2,3,7,2]
ghci> take 5 (cycle ["372 world"])
["372 world","372 world","372 world","372 world","372 world"]

레인지(ranges)

하스켈은 반복되는 패턴이 있는 리스트를 간단히 표현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ghci> [1,2,3,4,5,6,7,8,9,10]
[1,2,3,4,5,6,7,8,9,10]
ghci> [1..10]
[1,2,3,4,5,6,7,8,9,10]
ghci> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci> ['A'..'Z']
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
<간격이 반복되는 리스트>
ghci> [1,3..20]
[1,3,5,7,9,11,13,15,17,19]
ghci> ['a','c'..'z']
"acegikmoqsuwy"
ghci> [1,4..50]
[1,4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49]
<무한의 값의 리스트>
ghci> [1,2..] => 끝나지 않음 : 빠져 나오기 (ctrl+shift+c)
<무한 리스트에 함수를 적용>
ghci> take 4 [1,7..]
[1,7,13,19]
ghci> replicate 5 3
[3,3,3,3,3]
<부동소수점 수에 대한 주의>
ghci>[0.1, 0.4 .. 2.0]
[0.1,0.4,0.7000000000000001,1.0,1.2999999999999998,1.5999999999999996,1.8999999999999995]
=> 부동소수점 수는 유한한 정밀도를 가지고 있기 때문에 원하지 않는 값이 나올 수도 있다

리스트 +a

리스트에 수학적 표현을 나타낼 수 있습니다. 수학의 집합론 중 조건제시법 과 비슷한데 한가지 예를 들겠습니다.

{ 3 · x|x ∈ N, x ≤ 30 }

위 표현은 ‘30보다 작거나 같은 모든 자연수를 가지고 각각의 값에 3을 곱한 값들의 집합’입니다.
하스켈에서는 다음과 같이 표기 할 수 있습니다.

1
2
ghci> [x*3 | x <- [1..30]]
[3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60,63,66,69,72,75,78,81,84,87,90]

조건을 추가할 수 있습니다. 위 리스트에서 ‘50 이하의 원소만 포함’‘ 의 조건은 넣어보면 다음과 같이 , 이후로 조건을 넣을 수 있습니다.

1
2
3
4
5
6
ghci> [x*3 | x <- [1..30], x*3 <= 50]
[3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48]
<더 많은 조건을 넣을 경우>
ghci> [x*3 | x <- [1..30], x*3 <= 50, even x] => 짝수의 값만을 가질 조건.
[6,12,18,24,30,36,42,48]

20 이하의 홀수와 짝수의 리스트를 구해봅시다.

1
2
3
4
ghci> [ x | x <- [1..20], odd x ]
[1,3,5,7,9,11,13,15,17,19]
ghci> [ x | x <- [1..20], even x ]
[2,4,6,8,10,12,14,16,18,20]

짝수의 정수에서 20 이하의 수는 “down”, 20 이상의 수는 “up” 이라고 표현하라는 함수 evenNums 를 정의하면 다음과 같습니다.

1
2
3
ghci> let evenNums xs = [ if x < 20 then "down" else "up" | x <- xs, even x ]
ghci> evenNums [ 14..27 ]
["down","down","down","up","up","up","up"]

리스트와 리스트의 연산결과로 새로운 리스트를 만들어내는 예제입니다.

1
2
ghci>[ x + y | x <- [1..10], y <- [2,4]]
[3,5,4,6,5,7,6,8,7,9,8,10,9,11,10,12,11,13,12,14]

하스켈은 함수의 매개변수에 대응되는 표현을 _(밑줄)을 사용합니다.
다음은 length 함수를 구현해 놓은 것입니다.

1
2
3
ghci> let leng x = sum [1 | _ <- x ]
ghci> leng [1,2,3,4]
4

튜플(tuple)

튜플은 리스트와 비슷합니다만, 여러 타입이 포함될 수 있고 정해진 개수만 가질 수 있습니다.
리스트와 달리 튜플은 () 를 사용합니다.

1
2
ghci> (1,2,"world",True)
(1,2,"world",True)

리스트는 크기가 다른 것은 상관 없습니다만, 튜플은 크기가 다른 것은 다른 타입이라고 여겨집니다.
이로 인해서 리스트는 크기가 다른 것은 비교할 수 있지만 튜플은 크기가 다른 것은 비교할 수 없습니다.

(2개의 원소로 이루어진 튜플(pair), 3개의 원소로 이루어진 튜플(triple) 이라고 합니다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ghci> [[1,2,3], [4,5,6], [7,8,9,0]]
[[1,2,3],[4,5,6],[7,8,9,0]]
ghci> [(1,2,3), (4,5,6), (7,8,9,0)]
<interactive>:20:21: error:
• Couldn't match expected type ‘(t2, t1, t)’
with actual type ‘(Integer, Integer, Integer, Integer)’
• In the expression: (7, 8, 9, 0)
In the expression: [(1, 2, 3), (4, 5, 6), (7, 8, 9, 0)]
In an equation for ‘it’: it = [(1, 2, 3), (4, 5, 6), (7, 8, 9, 0)]
• Relevant bindings include
it :: [(t2, t1, t)] (bound at <interactive>:20:2)
ghci> [(1,2,3), (4,5,6), (7,8,9)]
[(1,2,3),(4,5,6),(7,8,9)]

페어(pair)를 이용한 유용한 함수들이 있습니다.

1
2
3
4
5
6
7
8
9
10
ghci> fst (372, "world") => 첫번째 값을 리턴합니다.
372
ghci> snd (372, "world") => 두번째 값을 리턴합니다.
"world"
<zip 함수를 사용해서 두개의 리스트로 페어리스트를 만듬>
ghci> zip [1,2,3,4,5] [101,102,103,104,105]
[(1,101),(2,102),(3,103),(4,104),(5,105)]
ghci> zip [1..] ["a", "b", "c", "d"]
[(1,"a"),(2,"b"),(3,"c"),(4,"d")]

튜플과 리스트로 문제를 하나 풀어보도록 하겠습니다.
피타고라스 정리를 이용하여 직각삼각형 각 변의 크기를 정하는 문제입니다.

조건 : 모든 변의 값은 정수, 모든 변의 값 10 이하, 모든 변의 합은 24

1
2
3
4
5
6
7
8
9
10
< 모든 변은 10이하 >
ghci>let sizes = [ (a,b,c) | c <- [1..10], a <- [1..10], b <- [1..10] ]
< 피타고라스 공식 조건을 추가 >
ghci>let pythagorean = [ (a,b,c) | c <- [1..10], a <- [1..c], b <- [1..a], a^2 + b^2 == c^2 ]
< 모든 변의 합=24 조건을 추가>
ghci>let triangles = [ (a,b,c) | c <- [1..10], a <- [1..c], b <- [1..a], a^2 + b^2 == c^2, a+b+c ==24 ]
ghci>triangles
[(8,6,10)]

하스켈 문법의 기본적인 내용을 훓어 봤습니다. 자바를 배울때와는 조금 다른 느낌으로 코딩하는 것은 명확하게 느껴집니다. 한 언어의 패러다임에 귀속되지 않고 다양한 패러다임을 경험할 수 있는 좋은 학습이 될 것 같습니다.

haskell functional programming