$ stack ghci
Configuring GHCi with the following packages:
GHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from /tmp/haskell-stack-ghci/2a3bbd58/ghci-script
ghci>
Functional : 프로그램의 기본 구성 요소는 함수입니다.
Pure : 하스켈의 함수는 side effects
가 없습니다.
Lazy : 변수 값이 사용될 때 결정됩니다.
Strongly typed : 하스켈의 모든 값과 식은 타입을 가집니다.
Type inferred : 컴파일러가 타입 추론을 합니다.
Garbage-collected : 하스켈은 garbage collection
에 대해 자동적으로 메모리 관리를 해줍니다.
Compiled : 하스켈은 compiled language
입니다.
하스켈은 함수의 인자로 함수를 줄 수 있습니다.
함수를 연달아 적용하려고 할 때 편리합니다.
Prelude> map length["abc", "abcdef"]
[3, 6]
[함수명] [인자] = [내용]
Ex)
Prelude> longerThanOne x = length x > 1
Prelude> longerThanOne "abc"
True
하스켈에서는 \
를 이용해서 이름이 없는 함수를 만들 수 있습니다.
한번만 사용하고 말 함수를 선언할 때 편리합니다.
\[함수명] -> [내용]
Ex)
Prelude> filter (\x -> length x > 1) ["abc", "d", "ef"]
["abc", "ef"]
하스켈에서는 인자중 일부를 비워두고 함수를 호출할 수 있습니다.
Prelude> mul3 x = x * 3
Prelude> map mul3 [1,2,3]
[3,6,9]
Prelude> map (*3) [1,2,3]
[3,6,9]
함수를 만들어서 처리해야 할 일을 부분 함수 호출을 이용해서 간단하게 처리할 수 있습니다.
하스켈에서는 원하는 데이터 타입을 만들 수 있습니다.
Prelude> data Character = Pororo | Pokemon | Unknown deriving Show
Prelude> Pororo
Pororo
Prelude> Pokemon
Pokemon
Prelude> Unknown
Unknown
Prelude> data Shape = Point | Rectangle Double Double | Circle Double deriving Show
Prelude> Point
Point
Prelude> Rectangle 10.0 20.0
Rectangle 10.0 20.0
Prelude> Circle 30.0
Circle 30.0
함수를 간결하게 짜기에 매우 유용합니다.
Prelude> data Character = Pororo | Pokemon | Unknown deriving Show
Prelude> :{
Prelude| menu 1 = Pororo
Prelude| menu 2 = Pokemon
Prelude| menu _ = Unknown
Prelude| :}
Prelude> menu 1
Pororo
Prelude> menu 2
Pokemon
Prelude> menu 7
Unknown
Prelude> data Shape = Point | Rectangle Double Double | Circle Double deriving Show
Prelude> :{
Prelude| area Point = 0
Prelude| area (Rectangle width height) = width * height
Prelude| area (Circle radius) = 2 * pi * radius
Prelude| :}
Prelude> area(Point)
0.0
Prelude> area(Rectangle 10 20)
200.0
Prelude> area(Circle 3)
18.84955592153876
타입이 같은 값들의 나열
Prelude> [1, 2, 3, 4, 5]
[1,2,3,4,5]
Prelude> []
[]
Prelude> [1, "Eva"]
<interactive>:38:2: error:
• No instance for (Num [Char]) arising from the literal ‘1’
• In the expression: 1
In the expression: [1, "Eva"]
In an equation for ‘it’: it = [1, "Eva"]
(수학) 집합 조건 제시법 : { x | 자연수에 속하는 x에 대하여 x는 짝수 }
(하스켈) 리스트 제시법 : [x | x <- [1..], even x]
Ex) 짝수로 이루어진 리스트에서 10개만 출력하기
Prelude> take 10 [x | x <- [1..], even x]
[2,4,6,8,10,12,14,16,18,20]
ghci> reverse("asdf")
"fdsa"
ghci> tail("asdf")
"sdf"
ghci> head("asdf")
'a'
Haskell | Python, Java or C |
---|---|
g h f 1 | g ( h, f, 1 ) |
g h ( f 1 ) | g ( h, f ( 1 ) ) |
g ( h f 1 ) | g ( h ( f , 1 ) ) |
g ( h ( f 1 ) ) | g ( h ( f (1) ) ) |
Haskell | Python, Java or C |
---|---|
a + b | a + b |
f a + g b | f ( a ) + g ( b ) |
f ( a + g b ) | f ( a + g ( b ) ) |
Type | Literals | Operations |
---|---|---|
Int | 1, 2, -3 | +, -, *, div, mod |
Integer | 1, -2, 90000000 | +, -, *, div, mod |
Double | 0.1, 2.5 | +, -, *, /, sqrt |
Bool | True, False | &&, ||, not |
String | "abcd", "" | reverse, ++ |
module Gold where
-- The golden ratio
phi :: Double
phi = (sqrt 5 + 1) / 2
polynomial :: Double -> Double
polynomial x = x ^ 2 - x - 1
f x = polynomial (polynomial x)
main = do
print (polynomial phi)
print (f phi)
module Gold where
Gold
라는 모듈을 작성
-- The golden ratio
한줄 주석
{-
hello
-}
여러줄 주석
phi :: Double
phi = (sqrt 5 + 1) / 2
phi
변수를 Double
형으로 선언 (타입 시그니처)
phi
변수에 sqrt(5+1) / 2
를 대입
polynomial :: Double -> Double
polynomial x = x ^ 2 - x - 1
polynomial
함수의 인자, 리턴값 모두 Double
형이다.
x = x ^ 2 -x - 1
연산을 하는 polynomial
함수 선언
f x = polynomial (polynomial x)
x
를 인자로 polynomial
함수를 호출한 결과를 다시 인자로 넣어서 polynomial
함수를 호출하는 함수 f
를 선언
main = do
print (polynomial phi)
print (f phi)
main
함수
do
⇾ IO 역할을 하는 문법 구문
ghci> polynomial x = x ^ 2 - x - 1
ghci> polynomial 3.0
5.0
인터프리터에서 한줄 한줄 작성하기
ghci> :{
ghci| polynomial :: Double -> Double
ghci| polynomial x = x ^ 2 - x - 1
ghci| :}
ghci> polynomial 3.0
5.0
인터프리터에서 여러줄 작성하기
--Example.hs
polynomial :: Double -> Double
polynomial x = x ^ 2 - x - 1
ghci> :load Example.hs
[1 of 1] Compiling Main ( Example.hs, interpreted )
Ok, one module loaded.
ghci> polynomial 3.0
5.0
파일로 작성후 인터프리터에서 로드해서 실행하기
파일을 수정했을 때
--Example.hs
polynomial :: Double -> Double
polynomial x = x ^ 2 - x - 5
ghci> :reload
[1 of 1] Compiling Main ( Example.hs, interpreted )
Ok, one module loaded.
ghci> polynomial 3.0
1.0
:reload
로 재로딩
문자열에 불리언을 붙이려고 했을 때 에러
ghci> "string" ++ True
<interactive>:13:13: error:
• Couldn't match expected type ‘[Char]’ with actual type ‘Bool’
• In the second argument of ‘(++)’, namely ‘True’
In the expression: "string" ++ True
In an equation for ‘it’: it = "string" ++ True
• Couldn't match expected type ‘[Char]’ with actual type ‘Bool’
[Char]
형이 나올거라고 기대했는데 Bool
형이 나오네
<interactive>:13:13: error:
인터프리터에서 13번째 줄 13번째 글자에서 에러 발생
In the expression: "string" ++ True
"String" ++ True
식에서 에러 발생
In an equation for ‘it’: it = "string" ++ True
it = "string" ++ True
부분에서 에러 발생
ghci> True + 1
<interactive>:14:6: error:
• No instance for (Num Bool) arising from a use of ‘+’
• In the expression: True + 1
In an equation for ‘it’: it = True + 1
• No instance for (Num Bool) arising from a use of ‘+’
숫자가 아닌 변수에 +
연산을 가했습니다.
ghci> True +
<interactive>:15:7: error:
parse error (possibly incorrect indentation or mismatched brackets)
parse error
: 구문이 완전하지 않다
ghci> 7 `div` 2
3
정수 나눗셈에서는 div
사용
ghci> 7.0 / 2.0
3.5
실수형 나눗셈에서는 /
사용
만약 정수형 변수에 실수형 나눗셈 연산을 가하면?
ghci> :{
ghci| halve :: Int -> Int
ghci| halve x = x / 2
ghci| :}
<interactive>:21:13: error:
• No instance for (Fractional Int) arising from a use of ‘/’
• In the expression: x / 2
In an equation for ‘halve’: halve x = x / 2
에러 발생
ghci> price = if product == "milk" then 1 else 2
연산자 | 설명 |
---|---|
== | 같다 |
< | 작다 |
<= | 작거나 같다 |
> | 크다 |
>= | 크거나 같다 |
/= | 다르다 |
ghci> "foo" == "bar"
False
ghci> 5.0 <= 7.0
True
ghci> 1 == 1
True
ghci> 2 /= 3
True
ghci> "bike" /= "bike"
False
ghci> :{
ghci| checkPassword password = if password == "swordfish"
ghci| then "You're in."
ghci| else "ACCESS DENIED!"
ghci| :}
ghci> :type checkPassword
checkPassword :: String -> String
ghci> checkPassword "Hello"
"ACCESS DENIED!"
ghci> checkPassword "swordfish"
"You're in."
ghci> absoluteValue n = if n < 0 then -n else n
ghci> :type absoluteValue
absoluteValue :: (Ord a, Num a) => a -> a
ghci> absoluteValue 123
123
ghci> absoluteValue (-123)
123
ghci> :{
ghci| login user password = if user == "unicorn73"
ghci| then if password == "f4bulous!"
ghci| then "unicorn73 logged in"
ghci| else "wrong password"
ghci| else "unkown user"
ghci| :}
ghci> :type login
login :: String -> String -> String
ghci> login "hello" "mypassword"
"unkown user"
ghci> login "unicorn73" "mypassword"
"wrong password"
ghci> login "unicorn73" "f4bulous!"
"unicorn73 logged in"
circleArea :: Double -> Double
circleArea r = pi * rsquare
where pi = 3.1415926
rsquare = r * r
ghci> :load main.hs
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
ghci> circleArea 5
78.539815
circleArea r = let pi = 3.1415926
rsquare = r * r
in pi * rsquare
ghci> :reload
Ok, one module loaded.
ghci> circleArea 5
78.539815
circleArea r = pi * square r
where pi = 3.1415926
square x = x * x
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
ghci> circleArea 5
78.539815
circleArea r = let pi = 3.1415926
square x = x * x
in pi * square r
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
ghci> circleArea 5
78.539815
하스켈에서는 한번 값에 변수 이름을 붙이면 변수 값을 바꿀 수 없습니다.
increment x = let x = x + 1
in x
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
ghci> increment 1
...
x = x + 1
이 아니라 1+1+1+1+...
라는 식으로 계산됩니다.
compute x = let a = x + 1
a = a * 2
in a
ghci> :reload
Ok, one module loaded.
ghci> compute 3
<interactive>:19:1: error:
• Variable not in scope: compute :: t0 -> t
• Perhaps you meant ‘compare’ (imported from Prelude)
다른 언어에서는 x에 1을 더한 값을 a 넣고 그 값을 다시 2를 곱해서 a에 넣는 식으로 해석되겠지만, 하스켈에서는 변수 a에 대한 값의 업데이트가 불가능하기 때문에 에러가 발생합니다.
x :: Int
x = 5
f :: Int -> Int
f x = 2 * x
g :: Int -> Int
g y = x where x = 6
h :: Int -> Int
h x = x where x = 3
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
local definitions
의 변수는 동일한 이름의 다른 변수를 가릴수 있습니다.
f 1 ==> 2
g 1 ==> 6
h 1 ==> 3
f x ==> 10
g x ==> 6
h x ==> 3
ghci> :reload
Ok, one module loaded.
greet :: String -> String -> String
greet "Finland" name = "Hei, " ++ name
greet "Italy" name = "Ciao, " ++ name
greet "England" name = "How do you do, " ++ name
greet _ name = "Hello, " ++ name
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
ghci> greet "Finland" "Pekka"
"Hei, Pekka"
ghci> greet "England" "Bob"
"How do you do, Bob"
ghci> greet "Greenland" "Jan"
"Hello, Jan"
describe :: Integer -> String
describe 0 = "zero"
describe 1 = "one"
describe 2 = "an even prime"
describe n = "the number " ++ show n
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
ghci> describe 0
"zero"
ghci> describe 2
"an even prime"
ghci> describe 7
"the number 7"
pattern matching
코드를 조건문으로 바꿔보면
describe n = if n == 0 then "zero"
else if n==1 then "one"
else if n==2 then "an even prime"
else "the number " + show n
login :: String -> String -> String
login "unicorn73" "f4bulous!" = "unicorn73 logged in"
login "unicorn73" _ = "wrong password"
login _ _ = "unkown user"
_
: 아무거나 들어와도 매칭이 된다.
ghci> login "user" "password"
"unkown user"
ghci> login "unicorn73" "password"
"wrong password"
ghci> login "unicorn73" "f4bulous!"
"unicorn73 logged in"
pattern matching
코드를 조건문으로 바꿔보면
login id pw = if id=="unicorn73:
then if pw=="f4bulous!" then "unicorn73 logged in"
else "wrong password"
else "unkown user"
- factorial n = { - n! - }
===============================================
(1) n = 1 : n! = 1 base
(2) n > 1 : n! = n x (n-1)! inductive
===============================================
n! = n * (n-1) * ... * 2 * 1
(n-1)! = (n-1) * ... * 2 * 1
factorial 1 = 1
factorial n = n * factorial(n-1)
ghci> factorial 5
120
ghci> factorial 4
24
- squareSum n = { - 1^2 + 2^2 + ... + n^2 - }
=================================================
(1) n = 1 : 1 ^ 2 ==> 1
(2) n > 1 : squareSum (n-1) + n^2
=================================================
squareSum n
= 1^2 + 2^2 + ... + (n-1)^2 + n^2
= ( 1^2 + 2^2 + ... + (n-1)^2 ) + n^2
= squareSum (n-1) + n^2
squareSum 1 = 1
squareSum n = n^2 + squareSum(n-1)
ghci> squareSum 3
14
ghci> squareSum 4
30
- fibonacci n = { - n번째 피보나치 수열의 값 - }
=================================================
n = 1: 1번째 피보나치 수열의 값 = 1
n = 2: 2번째 피보나치 수열의 값 = 1
n > 2:
n번째 피보나치 수열의 값 =
n-1번째 피보나치 수열의 값 + n-2번째 피보나치 수열의 값
=================================================
- 예시: fibonacci 7 = 13
1 2 3 4 5 6 7 8 ...
1 1 2 3 5 8 13 21 ...
fibonacci 1 = 1
fibonacci 2 = 1
fibonacci n = fibonacci(n-1) + fibonacci(n-2)
ghci> fibonacci 5
5
ghci> fibonacci 3
2
ghci> fibonacci 7
13
하스켈은 들여쓰기 칸을 맞춰서 해줘야 합니다.
ghci> :{
ghci| k = a + b
ghci| where a = 1
ghci| b = 1
ghci| :}
들여쓰기를 잘 맞춰주면 에러 없이 잘 컴파일 됩니다.
들여쓰기를 잘 맞춰주지 않으면
i x = let y = x+x+x+x+x+x
in div y 5
j x = let y = x+x+x
+x+x+x
in div y 5
k = a + b
where a = 1
b = 1
l = a + b
where
a = 1
b = 1
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
main.hs:2:1: error:
parse error (possibly incorrect indentation or mismatched brackets)
|
2 | in div y 5
| ^
Failed, no modules loaded.
첫번째 줄부터 에러가 발생합니다.
들여쓰기를 맞춰주면
i x = let y = x+x+x+x+x+x
in div y 5
j x = let y = x+x+x
+x+x+x
in div y 5
k = a + b
where a = 1
b = 1
l = a + b
where
a = 1
b = 1
1. 같이 그룹화 되어야할 것들은 같은 라인에 맞춘다.
2. 너무 길어서 여러줄에 해야 한다면 한두개 정도 공백을 넣어서 들여쓰기를 한다.
주어진 인자와 주어진 타입으로 재귀함수를 작성할때 주어진 인자만 가지고 변수가 부족한 경우 Helper Functions
을 사용할 수 있습니다.
helper variables
를 도입해서 재귀호출을 하는 Helper Functions
을 만들고 본 함수에서 Helper Functions
를 호출합니다.
repeatString n str = repeatHelper n str ""
repeatHelper n str result = if (n==0)
then result
else repeatHelper (n-1) str (result++str)
ghci> repeatString 3 "Hello "
"Hello Hello Hello "
패턴 매칭 이용
repeatString n str = repeatHelper n str ""
repeatHelper 0 _ result = result
repeatHelper n str result = repeatHelper (n-1) str (result++str)
--fibonacci numbers, fast version
fibonacci :: Integer -> Integer
fibonacci n = fibonacci' 0 1 n
fibonacci' :: Integer -> Integer -> Integer -> Integer
fibonacci' a b 1 = b
fibonacci' a b n = fibonacci' b (a+b) (n-1)
ghci> fibonacci 5
5
if then else
문을 간결하게 표현하고 싶을 때 사용합니다.
f x y z
| condition1 = something
| condition2 = other
| otherwise = somethingother
otherwise ==> True
모든 조건을 불만족 시키면 실행
Guards
는 패턴 매칭
과 달리 조건식에 범위가 올 수 있습니다.
describe :: Int -> String
describe n
| n == 2 = "Two"
| even n = "Even"
| n==3 = "Three"
| n>100 = "Big!!"
| otherwise = "The number "++show n
factorial n
| n<0 = -1
| n==0 = 1
| otherwise = n * factorial (n-1)
패턴 매칭
과 Guards
는 함께 사용할 수 있습니다.
guessAge :: String -> Int -> String
guessAge "Griselda" age
| age < 47 = "Too low!"
| age > 47 = "Too high!"
| otherwise = "Correct!"
guessAge "Hansel" age
| age < 12 = "Too low!"
| age > 12 = "Too high!"
| otherwise = "Correct!"
guessAge name age = "Wrong name!"
ghci> guessAge "Griselda" 30
"Too low!"
ghci> guessAge "Griselda" 60
"Too high!"
ghci> guessAge "Griselda" 47
"Correct!"
ghci> guessAge "Bob" 30
"Wrong name!"
ghci> guessAge "Hansel" 10
"Too low!"
하스켈에서 리스트의 원소는 같은 타입으로 이루어져 있어야 합니다.
ghci> [1,2,4,8]
[1,2,4,8]
ghci> [True, False]
[True,False]
ghci> ["Hi", "Bye"]
["Hi","Bye"]
ghci> [ [1,2], [3,4] ]
[[1,2],[3,4]]
ghci> [1,5,"hi"]
<interactive>:15:2: error:
• No instance for (Num String) arising from the literal ‘1’
• In the expression: 1
In the expression: [1, 5, "hi"]
In an equation for ‘it’: it = [1, 5, "hi"]
head :: [a] -> a -- 첫번째 원소 반환
tail :: [a] -> [a] -- 첫번째 원소를 제외한 모든 원소 반환
init :: [a] -> [a] -- 마지막 원소를 제외한 모든 원소 반환
take :: Int -> [a] -> [a] -- 앞에서 부터 n개의 원소 반환
drop :: Int -> [a] -> [a] -- 앞에서 부터 n개의 원소를 제외한 원소 반환
(++) :: [a] -> [a] -> [a] -- 리스트 합치기
(!!) :: [a] -> Int -> a -- 리스트 인덱스 접근
reverse :: [a] -> [a] -- 리스트 뒤집기
null :: [a] -> Bool -- 리스트 비어 있는지 확인
length :: [a] -> Int -- 리스트 길이 확인
ghci> list = [1..10]
ghci> head list
1
ghci> tail list
[2,3,4,5,6,7,8,9,10]
ghci> init list
[1,2,3,4,5,6,7,8,9]
ghci> take 5 list
[1,2,3,4,5]
ghci> drop 5 list
[6,7,8,9,10]
ghci> list2 = [11..20]
ghci> list ++ list2
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
ghci> list !! 0
1
ghci> reverse list
[10,9,8,7,6,5,4,3,2,1]
ghci> null list
False
ghci> length list
10
ghci> import Data.List
ghci> sort [1, 0, 5, 3]
[0,1,3,5]
하스켈에서는 한번 값에 이름을 붙여 변수를 만들면 그 값을 변경할 수 없습니다.
Prelude> list = [1,2,3,4]
Prelude> reverse list
[4,3,2,1]
Prelude> list
[1,2,3,4]
Prelude> drop 2 list
[3,4]
Prelude> list
[1,2,3,4]
list
에 reverse
함수와 drop
함수를 사용해보면 원래 리스트의 값은 변하지 않고 새로운 리스트가 반환됩니다.
Mutability vs Immutability
Mutability
: 변수 값을 변경할 수 없어서 새로운 변수를 만들어 반환하는 Immutability
와 달리 그냥 변수 값을 변경하여 반환하면 되기 때문에 메모리가 절약됩니다.
Immutability
: 여러 함수에서 변수를 포인터로 참조하고 있는 경우 특정 함수에서 변수 값을 변경해 버렸을 때 다른 함수의 실행에 영향이 생겨 예기치 못한 오류가 발생할 수 있는데, 변수의 값의 변경이 불가능하면 이러한 오류를 방지할 수 있습니다.
Type Inference (타입 변수)
: 어떠한 타입도 될 수 있는 변수
[a] -> a
-------------------------------------------------
[a] ==> 여러 타입이 올 수 있는 리스트
a ==> 여러 타입이 올 수 있는 변수
Polymorphism (다형성)
: 여러 타입을 가질 수 있는 것
Polymorphism
함수 : 여러 타입이 올 수 있는 함수
head :: [a] -> a
Type Inference (타입 유추)
: 타입을 유추하는 것
head :: [a] -> a
head [True,False] :: Bool
-------------------------------------------------
[a] -> a
[Bool] -> a
a ==> Bool
f xs ys = [head xs, head ys]
-------------------------------------------------
head :: [a] -> a
xs ==> [a]
ys ==> [a]
f xs ys ==> [a]
f :: [a] -> [a] -> [a]
f :: [a] -> [a] -> [a]
g zs = f "Moi" zs
-------------------------------------------------
"Moi" ==> [a]
zs ==> [a]
String ==> [Char]
[a] ==> [Char]
zs ==> [Char]
g zs ==> [a]
g zs ==> [Char]
g :: [Char] -> [Char]
type parameter
: 리스트 안에 들어오는 타입
Ex) [Char]
의 type parameter
는 Char
parameterized type
: 리스트처럼 파라미터를 갖는 타입
parametric polymorphism
: 타입 변수를 사용하는 형태
함수를 호출했을 때 정상 뿐만 아니라 비정상 결과도 나올때 사용합니다.
Nothing
과 Just
생성자를 사용합니다.
Nothing
은 그 자체가 생성자, Just
는 또다른 파라미터를 받습니다.
정상적인 경우에는 Just parameter
를 리턴하고 비정상적인 경우에는 Nothing
을 리턴합니다.
Prelude> Nothing :: Maybe Int
Nothing
Prelude> Just 0 :: Maybe Int
Just 0
Prelude> Just (-1) :: Maybe Int
Just (-1)
Prelude> Just "a camel"
Just "a camel"
Prelude> :t Just "a camel"
Just "a camel" :: Maybe [Char]
아래 처럼 login
함수를 짜면?
def login(pwd)
if pwd == "f4bulous!":
return "unicorn73"
else if pwd == "swordfish"
return "megahacker"
else
return ""
⇾ 로그인 실패를 빈 문자열로 가정하고 코드 처리
⇾ 시간 지나면 위 가정을 까먹고 코딩
⇾ 잘못된 코드를 짤 수 있음
⇾ Haskell
에서는 Nothing
을 통해 예외 처리 가능
-- given a password, return (Just username) if login succeeds, Nothing otherwise
login :: String -> Maybe String
login "f4bulous!" = Just "unicorn73"
login "swordfish" = Just "megahacker"
login _ = Nothing
비밀번호를 맞게 입력하면 Just username
을 리턴하고 잘못된 비밀번호를 입력하면 Nothing
을 리턴합니다.
*Main> login "f4bulous!"
Just "unicorn73"
*Main> login "swordfish"
Just "megahacker"
*Main> login "aaa"
Nothing
-- Multiply an Int with a Maybe Int. Nothing is treated as no multiplication at all.
perhapsMultiply :: Int -> Maybe Int -> Int
perhapsMultiply i Nothing = i
perhapsMultiply i (Just j) = i*j -- Note how j denotes the value inside the Just
곱셈을 할때 두번째 숫자가 안주어지면 첫번째 숫자를 반환하고 두 숫자 모두 주어지면 계산 결과를 리턴합니다.
*Main> perhapsMultiply 3 (Just 2)
6
*Main> perhapsMultiply 5 Nothing
5
safeHead :: [a] -> Maybe a
safeHead xs = if null xs then Nothing else Just (head xs)
head
를 할때 빈 리스트가 주어지면 오류가 발생하는데,
Maybe Type
을 이용하면 safeHead
함수를 만들수 있습니다.
*Main> safeHead [1..5]
Just 1
*Main> safeHead []
Nothing
값이 들어있는 리스트가 들어오면 head
연산 결과를 리턴하고 비어있는 리스트가 들어오면 Nothing
을 리턴합니다.
비정상적인 상황이 여러개 일때 이를 구분하기 위해 사용합니다.
Either a b
로 두개의 인자를 받습니다.
Left and Right
두개의 생성자를 갖습니다.
Left
생성자는 a
라는 인자를 갖고 Right
생성자는 b
라는 인자를 갖습니다.
*Main> Left 0 :: Either Int Bool
Left 0
*Main> Left "Abc" :: Either String Int
Left "Abc"
*Main> Right True :: Either Int Bool
Right True
readInt :: String -> Either String Int
readInt "0" = Right 0
readInt "1" = Right 1
readInt s = Left ("Unsupported string: " ++ s)
*Main> readInt "0"
Right 0
*Main> readInt "1"
Right 1
*Main> readInt "5"
Left "Unsupported string: 5"
iWantAString :: Either Int String -> String
iWantAString (Right str) = str
iWantAString (Left number) = show number
*Main> iWantAString (Left 123)
"123"
*Main> iWantAString (Right "abc")
"abc"
[Left 1, Right "foo", Left 2] :: [Either Int String]
case <value> of <pattern> -> <expression>
<pattern> -> <expression>
describe :: Integer -> String
describe 0 = "zero"
describe 1 = "one"
describe 2 = "an even prime"
describe n = "the number " ++ show n
describe :: Integer -> String
describe n = case n of 0 -> "zero"
1 -> "one"
2 -> "an even prime"
n -> "the number " ++ show n
패턴 매칭을 사용했을 때 보다 더 간결하게 코드를 짤 수 있습니다.
parse 한다 ⇾ 구조를 파악한다.
-- parse country code into country name, returns Nothing if code not recognized
parseCountry :: String -> Maybe String
parseCountry "FI" = Just "Finland"
parseCountry "SE" = Just "Sweden"
parseCountry _ = Nothing
flyTo :: String -> String
flyTo countryCode = case parseCountry countryCode of Just country -> "You're flying to " ++ country
Nothing -> "You're not flying anywhere"
case 문을 사용하지 않고 패턴 매칭을 사용했다면, "You're flying to {Country Name}"
을 여러번 입력해야 해서 반복으로 인해 비효율적이었을 것입니다.
motivate :: String -> String
motivate "Monday" = "Have a nice week at work!"
motivate "Tuesday" = "You're one day closer to weekend!"
motivate "Wednesday" = "3 more day(s) until the weekend!"
motivate "Thursday" = "2 more day(s) until the weekend!"
motivate "Friday" = "1 more day(s) until the weekend!"
motivate _ = "Relax! You don't need to work today!"
motivate :: String -> String
motivate day = case distanceToSunday day of
6 -> "Have a nice week at work!"
5 -> "You're one day closer to weekend!"
n -> if n > 1
then show (n - 1) ++ " more day(s) until the weekend!"
else "Relax! You don't need to work today!"
area :: String -> Double -> Double
area "square" x = square x
area "circle" x = pi * square x
where square x = x * x
where 절은 바로 위에 줄 까지만 영향을 미치기 때문에, 컴파일 에러가 발생합니다.
area :: String -> Double -> Double
area shape x = case shape of
"square" -> square x
"circle" -> pi * square x
where square x = x*x
case문을 사용하면 위의 두줄이 한줄의 case문으로 묶이기 때문에, where절을 사용했을 때 에러가 발생하지 않습니다.
distanceToSunday :: String -> Int
distanceToSunday "Monday" = 6
distanceToSunday "Tuesday" = 5
distanceToSunday "Wednesday" = 4
distanceToSunday "Thursday" = 3
distanceToSunday "Friday" = 2
distanceToSunday "Saturday" = 1
distanceToSunday "Sunday" = 0
distanceToSunday :: String -> Int
distanceToSunday d = case d of
"Monday" -> 6
"Tuesday" -> 5
"Wednesday" -> 4
"Thursday" -> 3
"Friday" -> 2
"Saturday" -> 1
"Sunday" -> 0
반복이 너무 많을 때 반복을 줄이기 위해 case문을 사용할 수 있습니다.
case number of 0 -> "zero"
1 -> "one"
_ -> "not zero or one"
-- getElement (Just i) gets the ith element (counting from zero) of a list, getElement Nothing gets the last element
getElement :: Maybe Int -> [a] -> a
getElement (Just i) xs = xs !! i
getElement Nothing xs = last xs
direction :: Either Int Int -> String
direction (Left i) = "you should go left " ++ show i ++ " meters!"
direction (Right i) = "you should go right " ++ show i ++ " meters!"
applyTo1 :: (Int -> Int) -> Int
applyTo1 f = f 1
addThree :: Int -> Int
addThree x = x + 3
applyTo1
의 첫번째 인자는 Int
형 인자를 받아 Int
형 값을 반환하는 함수가 들어갑니다.
그래서 applyTo1
의 첫번째 인자로 addThree
를 줄 수 있습니다.
applyTo1 addThree
==> addThree 1
==> 1 + 3
==> 4
*Main> applyTo1 addThree
4
doTwice :: (a -> a) -> a -> a
doTwice f x = f (f x)
doTwice addThree 3
doTwice :: (Int -> Int) -> Int -> Int
doTwice addThree 3
==> addThree (addThree 3)
==> addThree 6
==> 9
*Main> doTwice addThree 3
9
makeCool :: String -> String
makeCool str = "WOW" ++ str ++ "!"
doTwice makeCool "Haskell"
(String -> String) -> String -> String
doTwice makeCool "Haskell"
==> makeCool (MakeCool "Haskell")
==> makeCool "WOWHaskell!"
==> WOWWOWHaskell!!
*Main> doTwice makeCool "Haskell"
"WOWWOWHaskell!!"
map :: (a -> b) -> [a] -> [b]
리스트의 모든 원소에 해당 함수를 적용합니다.
*Main> map addThree [1..3]
[4,5,6]
filter :: (a -> Bool) -> [a] -> [a]
주어진 리스트에서 조건을 만족하는 것만 끄집어냅니다.
positive :: Int -> Bool
positive x = x > 0
*Main> filter positive [0,1,-1,3,-3]
[1,3]
onlyPositive xs = filter positive xs
mapBooleans f = map f [False,True]
filter :: (a -> Bool) -> [a] -> [a]
filter positive xs
positive :: Int -> Bool
a => Int
onlyPositive :: [Int] -> [Int]
*Main> :t onlyPositive
onlyPositive :: [Int] -> [Int]
*Main> :t mapBooleans
mapBooleans :: (Bool -> b) -> [b]
wrapJust xs = map Just xs
*Main> wrapJust [1..5]
[Just 1,Just 2,Just 3,Just 4,Just 5]
-- a predicate that checks if a string is a palindrome
palindrome :: String -> Bool
palindrome str = str == reverse str
-- palindromes n takes all numbers from 1 to n, converts them to strings using show, and keeps only palindromes
palindromes :: Int -> [String]
palindromes n = filter palindrome (map show [1..n])
*Main> palindrome "1331"
True
*Main> palindromes 150
["1","2","3","4","5","6","7","8","9","11","22","33","44","55","66","77","88","99","101","111","121","131","141"]
substringsOfLength :: Int -> String -> [String]
substringsOfLength n string = map shorten (tails string)
where shorten s = take n s
*Main> substringsOfLength 3 "hello"
*Main> ["hel","ell","llo","lo","o",""]
부분적인 인자만 가지고 함수를 호출하는 것입니다.
Prelude> add a b = a + b
Prelude> map (add 3) [1,2,3]
[4,5,6]
부분 호출을 사용하지 않으면?
addThree = add 3
map addThree [1,2,3]
addThree
라는 함수를 만들어야 합니다.
between :: Integer -> Integer -> Integer -> Bool
between lo high x = x < high && x > lo
*Main> between 3 7 5
True
*Main> between 3 6 8
False
*Main> (between 1 5) 2
True
*Main> let f = between 1 5 in f 2
True
*Main> map (between 1 3) [1,2,3]
[False,True,False]
*Main> :t between
between :: Integer -> Integer -> Integer -> Bool
*Main> :t between 1
between 1 :: Integer -> Integer -> Bool
*Main> :t between 1 2
between 1 2 :: Integer -> Bool
*Main> :t between 1 2 3
between 1 2 3 :: Bool
between 1 2 3
= (between 1) 2 3
= ((between 1) 2) 3
*Main> map (drop 1) ["Hello", "World!"]
["ello","orld!"]
*Main> map (*3) [1..5]
[3,6,9,12,15]
*Main> map (/2) [1..5]
[0.5,1.0,1.5,2.0,2.5]
*Main> (+) 1 5
6
중위 표기법으로 쓰던 연산자를 전위 표기법으로 쓰고 싶으면 ()
로 묶어줘야 합니다.
Prelude> zipWith + [1..3] [4..6]
<interactive>:1:11: error:
중위 표기 연산자인 +
를 괄호로 묶어 주지 않아서 오류가 발생했습니다.
Prelude> zipWith (+) [1..3] [4..6]
[5,7,9]
Prelude> (+1) `map` [1,2,3]
[2,3,4]
원래 전위 표기법로 사용해야 하는 함수를 중위 표기법으로 사용하고 싶으면 ` `로 묶어 줘야 합니다.
let big x = x > 7 in filter big [1,10,100]
Prelude> filter (\x -> x > 7) [1,10,100]
[10,100]
람다식을 사용하는게 훨씬 간결합니다.
Prelude> filter (\x -> reverse x == x) ["ABCD","ABBA","AAA"]
["ABBA","AAA"]
(f.g) x ==> f (g x)
.
연산자는 두개의 함수를 조합하는 연산자입니다.
double x = 2 * x
quadruple = double.double
*Main> quadruple 4
16
*Main> f = quadruple . (+1)
*Main> g = (+1) . quadruple
*Main> f 5
24
*Main> g 6
25
*Main> third = head . tail . tail
*Main> third [1..5]
3
*Main> notEmpty x = not (null x)
*Main> filter notEmpty [[1,2,3],[],[4]]
[[1,2,3],[4]]
*Main> filter (not . null) [[1,2,3],[],[4]]
[[1,2,3],[4]]
f $ x ==> f x
괄호 붙이기 귀찮을 때 $
를 사용합니다.
*Main> head (reverse "abcd")
'd'
*Main> head $ reverse "abcd"
'd'
*Main> reverse (map head (map reverse (["Haskell","pro"] ++ ["dodo","lyric"])))
"cool"
*Main> (reverse . map head . map reverse) (["Haskell","pro"] ++ ["dodo","lyric"])
"cool"
*Main> reverse . map head . map reverse $ ["Haskell","pro"] ++ ["dodo","lyric"]
"cool"
substringsOfLength :: Int -> String -> [String]
substringsOfLength n string = map shorten (tails string)
where shorten s = take n s
whatFollows :: Char -> Int -> String -> [String]
whatFollows c k string = map tail (filter match (substringsOfLength (k+1) string))
where match sub = take 1 sub == [c]
1. substringsOfLength 함수 사용 X
whatFollows c k string = map tail (filter match (map shorten (tails string)))
where shorten s = take (k+1) s
match sub = take 1 sub == [c]
2. 부분 호출
whatFollows c k string = map tail (filter match (map (take (k+1)) (tails string)))
where match sub = take 1 sub == [c]
3. .
, $
연산자 사용
whatFollows c k string = map tail . filter match . map (take (k+1)) $ tails string
where match sub = take 1 sub == [c]
4. 람다식 사용
whatFollows c k string = map tail . filter (\sub -> take 1 sub == [c]) . map (take (k+1)) $ tails string
5. string 없애기
whatFollows c k = map tail . filter (\sub -> take 1 sub == [c]) . map (take (k+1)) . tails
6. 람다를 연산자 섹션으로 변경
\sub -> take 1 sub == [c]
=== \sub -> (==[c]) (take 1 sub)
=== \sub -> (==[c]) ((take 1) sub)
=== \sub -> ((==[c]) . (take 1)) sub
=== ((==[c]) . (take 1))
=== ((==[c]) . take 1)
whatFollows c k = map tail . filter ((==[c]) . take 1) . map (take (k+1)) . tails
Prelude> takeWhile even [2,4,1,2,3]
[2,4]
Prelude> dropWhile even [2,4,1,2,3]
[1,2,3]
takeWhile
함수는 Bool
값이 True
이면 뽑아라
dropWhile
함수는 Bool
값이 True
이면 버려라
Prelude> elem 3 [1..3]
True
elem
함수는 첫번째로 주어진 인자가 두번째로 주어진 리스트에 포함되어 있나?
findSubstring :: String -> String -> String
findSubstring chars = takeWhile (\x -> elem x chars)
. dropWhile (\x -> not $ elem x chars)
findSubstring
함수는 두번째로 오는 문자열에서 첫번째로 오는 문자가 포함된 가장 길고 앞에 오는 서브 스트링을 반환
*Main> findSubstring "a" "bbaabaaaab"
"aa"
*Main> findSubstring "abcd" "xxxyyyzabaaxxabcd"
"abaa"
*Main> zipWith (++) ["John","Mary"] ["Smith","Cooper"]
["JohnSmith","MaryCooper"]
*Main> zipWith take [4,3] ["Hello","Warden"]
["Hell","War"]
zipWith
함수는 리스트의 같은 인덱스 원소끼리 합칩니다.
*Main> id 3
3
*Main> map id [1..3]
[1,2,3]
id
함수는 인자를 그대로 반환합니다.
*Main> filter id [True,False,True,True]
[True,True,True]
*Main> dropWhile id [True,True,False,True,False]
[False,True,False]
*Main> const 3 0
3
*Main> map (const 5) [1..4]
[5,5,5,5]
*Main> filter (const True) [1..5]
[1,2,3,4,5]
const
함수는 뒤에 오는 인자를 까먹고 앞에 오는 인자만 리턴합니다.
:
연산자는 앞에 원소를 뒤에 리스트에 넣습니다.
*Main> 1 : []
[1]
*Main> 1 : [2,3]
[1,2,3]
*Main> tail (1 : [2,3])
[2,3]
*Main> head (1 : [2,3])
1
리스트를 만드는 연산자에는 :
와 []
가 있습니다.
*Main> 1 : 2 : 3 : []
[1,2,3]
[x,y,z]
=> x:y:z:[]
=> x:(y:(z:[]))
descend 0 = []
descend n = n : descend(n-1)
*Main> descend 4
[4,3,2,1]
*Main> descend 10
[10,9,8,7,6,5,4,3,2,1]
descend
함수를 재귀적으로 호출하여 주어진 숫자부터 1까지를 원소로 갖는 리스트 생성
_iterate f 0 x = [x]
_iterate f n x = x : _iterate f (n-1) (f x)
*Main> :type iterate
iterate :: (a -> a) -> a -> [a]
*Main> _iterate (*2) 4 3
[3,6,12,24,48]
_iterate (*2) 4 3
==> 3 : _iterate (*2) 3 6
==> 3 : 6 : _iterate (*2) 2 12
==> 3 : 6 : 12 : _iterate (*2) 1 24
==> 3 : 6 : 12 : 24 : _iterate (*2) 0 48
==> 3 : 6 : 12 : 24 : [48]
==> [3, 6, 12, 24, 48]
*Main> xs = "terve"
*Main> _iterate tail (length xs) xs
["terve","erve","rve","ve","e",""]
split :: Char -> String -> [String]
split c [] = []
split c xs = start : split c (drop 1 rest)
where start = takeWhile (/=c) xs
rest = dropWhile (/=c) xs
fooxzoo
를 기준으로
start
: foo
rest
: xzoo
split c (drop 1 rest)
: zoo
foo : zoo
*Main> split 'x' "fooxxbarxquux"
["foo","","bar","quu"]
*Main> split 'x' "fooxzoo"
["foo","zoo"]
myhead :: [Int] -> Int
myhead [] = -1
myhead (first:rest) = first
mytail :: [Int] -> [Int]
mytail [] = []
mytail (first:rest) = rest
myhead [1,2,3,4,5]
==> myhead (1:(2:(3:(4:(5:[])))))
==> 1
*Main> myhead [1,2,3,4,5]
1
tail [1,2,3,4,5]
tail (1:(2:(3:(4:(5:[])))))
==> (2:(3:(4:(5:[]))))
==> [2,3,4,5]
*Main> tail [1,2,3,4,5]
[2,3,4,5]
sumFirstTwo :: [Integer] -> Integer
-- this equation gets used for lists of length at least two
sumFirstTwo (a:b:_) = a+b
-- this equation gets used for all other lists (i.e. lists of length 0 or 1)
sumFirstTwo _ = 0
(a:b:_) ==> (a:(b:_))
*Main> sumFirstTwo [1]
0
*Main> sumFirstTwo [1,2]
3
*Main> sumFirstTwo [1,2,4]
3
describeList :: [Int] -> String
describeList [] = "an empty list"
describeList (x:[]) = "a list with one element"
describeList (x:y:[]) = "a list with two elements"
describeList (x:y:z:xs) = "a list with at least three elements"
*Main> describeList [1,3]
"a list with two elements"
*Main> describeList [1,2,3,4,5]
"a list with at least three elements"
[1,3]
==> (x:y:[])
==> 원소가 두개인 리스트
[1,2,3,4,5]
==> (1:2:3:[4,5])
==> 원소가 3개 이상인 리스트
sumNumbers :: [Int] -> Int
sumNumbers [] = 0
sumNumbers (x:xs) = x + sumNumbers xs
*Main> sumNumbers [1,2,3]
6
*Main> sumNumbers [1,2,3,4]
10
리스트 원소들의 합을 구하는 함수
myMaximum :: [Int] -> Int
myMaximum [] = 0 -- actually this should be some sort of error...
myMaximum (x:xs) = go x xs
where go biggest [] = biggest
go biggest (x:xs) = go (max biggest x) xs
리스트 원소중 가장 큰 원소를 찾는 함수
myMaximum [1,5,3,12]
==> go 1 [5,3,12]
==> go 5 [3,12]
==> go 5 [12]
==> go 12 []
==> 12
*Main> myMaximum [1,5,3,12]
12
countNothings :: [Maybe a] -> Int
countNothings [] = 0
countNothings (Nothing : xs) = 1 + countNothings xs
countNothings (Just _ : xs) = countNothings xs
Nothing
이 몇개인지 세는 함수
*Main> countNothings [Nothing, Just 1, Just 3]
1
*Main> countNothings [Nothing, Just 1, Nothing]
2
doubleList :: [Int] -> [Int]
doubleList [] = []
doubleList (x:xs) = 2*x : doubleList xs
doubleList [1,2,3]
=== doubleList (1:(2:(3:[])))
==> 2*1 : doubleList (2:(3:[]))
==> 2*1 : (2*2 : doubleList (3:[]))
==> 2*1 : (2*2 : (2*3 : doubleList []))
==> 2*1 : (2*2 : (2*3 : []))
=== [2*1, 2*2, 2*3]
==> [2,4,6]
*Main> doubleList [1,2,3]
[2,4,6]
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
비어있는 리스트가 들어오면 그대로 리턴
(함수 f
를 x
에 적용) : (xs
로 map
함수 재귀호출)
==> 리스트의 모든 원소에 f
함수가 적용됩니다.
filter :: (a -> Bool) -> [a] -> [a]
filter _pred [] = []
filter pred (x:xs)
| pred x = x : filter pred xs
| otherwise = filter pred xs
x
에 pred
함수를 적용한 결과가 True
이면 (xs
) : (xs
로 filter
함수 재귀호출)
아니면 xs
로 filter
함수 재귀호출
Tail Recursion
: 재귀함수의 위치가 함수의 맨 마지막에 위치한 경우, 돌아왔을 때 크게 할일이 없음
-- Not tail recursive!
doubleList :: [Int] -> [Int]
doubleList [] = []
doubleList (x:xs) = 2*x : doubleList xs
-- Tail recursive version
doubleList :: [Int] -> [Int]
doubleList xs = go [] xs
where go result [] = result
go result (x:xs) = go (result++[2*x]) xs
Tail recursion
vs Not Tail recursion
Tail recursion
이 빠를 때도 있고 Not Tail recursion
이 빠를수도 있다.
그러면 어떤걸 써야 하나?
그냥 코드를 읽기 편한쪽으로 짜면된다.
Mapping:
*Main> [ 2*i | i <- [1,2,3] ]
[2,4,6]
Filtering:
*Main> [ i | i <- [1..7], even i ]
[2,4,6]
Mapping + Filtering:
[ f x | x <- lis, p x ]
==> map f (filter p lis)
*Main> [ first ++ " " ++ last | first <- ["John", "Mary"], last <- ["Smith","Cooper"] ]
["John Smith","John Cooper","Mary Smith","Mary Cooper"]
first <- ["John", "Mary"]
last <- ["Smith", "Cooper"]
do something with (first, last)
리스트 제시법을 map
으로 바꿔보면
concat (map (\x -> map (\y -> first ++ " " ++ last) ["Smith", "Cooper"]) ["John", "Marry"])
concat :: [ [a] ] -> [a]
concat [] = xs
concat (x:xs) = xs ++ concat xss
리스트 안의 리스트를 빼내는 함수
Ex) [ ["John"], ["Smith"] ]
==> ["John", "Smith"]
*Main> [ reversed | word <- ["this","is","a","string"], let reversed = reverse word ]
["siht","si","a","gnirts"]
식이 복잡해지면 로컬 변수를 만들 수 있는데, 앞에 let
이라는 키워드를 사용해야 합니다.
firstLetters string = [ char | (char:_) <- words string ]
*Main> words "Hello World!"
["Hello","World!"]
words string
에 의해서 char
는 첫번째 단어의 첫번째 문자가 되고 _
는 두번째 단어의 첫번째 문자가 됩니다.
*Main> firstLetters "Hello World!"
"HW"
!#$%&*+./<=>?@\^|-~
연산자를 이용해서 커스텀 연산자를 만들 수 있습니다.
연산자는 (
)
로 감싸줘야 합니다.
만들어진 연산자는 함수입니다.
(<+>) :: [Int] -> [Int] -> [Int]
xs <+> ys = zipWith (+) xs ys
*Main> [1,2,3] <+> [4,5,6]
[5,7,9]
(+++) :: String -> String -> String
a +++ b = a ++ " " ++ b
*Main> "Hello" +++ "World"
"Hello World"
타입을 추론하고 싶은 곳에 _
나 _name
을 넣어주면 컴파일러가 타입을 추론해줍니다.
ghci> filter _hole [True,False]
<interactive>:3:8: error:
• Found hole: _hole :: Bool -> Bool
Or perhaps ‘_hole’ is mis-spelled, or not in scope
• In the first argument of ‘filter’, namely ‘_hole’
In the expression: filter _hole [True, False]
In an equation for ‘it’: it = filter _hole [True, False]
• Relevant bindings include
it :: [Bool] (bound at <interactive>:3:1)
Valid hole fits include
not :: Bool -> Bool
(imported from ‘Prelude’ at main.hs:1:1
(and originally defined in ‘GHC.Classes’))
id :: forall a. a -> a
with id @Bool
(imported from ‘Prelude’ at main.hs:1:1
(and originally defined in ‘GHC.Base’))
pred :: forall a. Enum a => a -> a
with pred @Bool
(imported from ‘Prelude’ at main.hs:1:1
(and originally defined in ‘GHC.Enum’))
succ :: forall a. Enum a => a -> a
with succ @Bool
(imported from ‘Prelude’ at main.hs:1:1
(and originally defined in ‘GHC.Enum’))
keepElements :: [a] -> [Bool] -> [a]
keepElements xs bs = _doIt (zip xs bs)
main.hs:2:22: error:
• Found hole: _doIt :: [(a, Bool)] -> [a]
Where: ‘a’ is a rigid type variable bound by
the type signature for:
keepElements :: forall a. [a] -> [Bool] -> [a]
at main.hs:1:1-36
Or perhaps ‘_doIt’ is mis-spelled, or not in scope
• In the expression: _doIt (zip xs bs)
In an equation for ‘keepElements’:
keepElements xs bs = _doIt (zip xs bs)
• Relevant bindings include
bs :: [Bool] (bound at main.hs:2:17)
xs :: [a] (bound at main.hs:2:14)
keepElements :: [a] -> [Bool] -> [a] (bound at main.hs:2:1)
Valid hole fits include
mempty :: forall a. Monoid a => a
with mempty @([(a, Bool)] -> [a])
(imported from ‘Prelude’ at main.hs:1:1
(and originally defined in ‘GHC.Base’))
서로 다른 타입들의 값을 하나로 묶을 수 있는 자료 구조
Type | Example |
---|---|
(String, String) | ("Hello", "Hello!") |
(Int, Bool) | (1, True) |
fsd
함수 : 튜플에서 앞에 값을 리턴
snd
함수 : 튜플에서 뒤에 값을 리턴
fst :: (a, b) -> a
snd :: (a, b) -> b
ghci> fst ("Hello", "World")
"Hello"
ghci> snd ("Hello", "World")
"World"
zip
함수 : 두 리스트에서 같은 인덱스의 원소끼리 묶는 함수
unzip
함수 : zip
한 값을 다시 푸는 함수
ghci> zip [1,2,3] [True,False,True]
[(1,True),(2,False),(3,True)]
ghci> unzip it
([1,2,3],[True,False,True])
partition
함수 : 테스트 함수와 리스트를 받아서 테스트를 통과한 값의 리스트와 통과하지 못한 값의 리스트를 반환합니다.
ghci> import Data.List
ghci> partition (>0) [-1,1,-4,3,2,0]
([1,3,2],[-1,-4,0])
swap
함수 : 튜플의 원소를 서로 바꿉니다.
swap :: (a, b) -> (b, a)
swap (x, y) = (y, x)
ghci> swap ("Hello", False)
(False,"Hello")
sumIf
함수 : 튜플 쌍에서 첫번째 원소가 True
인 쌍의 숫자만 다 더하겠다.
sumIf :: [(Bool,Int)] -> Int
sumIf [] = 0
sumIf ((True,x):xs) = x + sumIf xs
sumIf ((False,_):xs) = sumIf xs
ghci> sumIf [(True,1),(False,10),(True,100)]
101
sumNumbers :: [Int] -> Int
sumNumbers [] = 0
sumNumbers (x:xs) = x + sumNumbers xs
myMaximum :: [Int] -> Int
myMaximum [] = 0
myMaximum (x:xs) = go x xs
where go biggest [] = biggest
go biggest (x:xs) = go (max biggest x) xs
countNothings :: [Maybe a] -> Int
countNothings [] = 0
countNothings (Nothing : xs) = 1 + countNothings xs
countNothings (Just _ : xs) = countNothings xs
sumNumbers
함수 : 리스트 원소의 합을 구하는 함수
myMaximum
함수 : 리스트의 최대값을 구하는 함수
countNothings
함수 : Nothing
의 갯수를 세는 함수
foldr
함수를 이용하면 위와 같이 리스트를 다루는 수많은 함수들을 재작성할 수 있습니다.
foldr
함수는 Foldable data type(리스트 같은 타입)
을 오른쪽 끝에서 부터 차근 차근 계산해 나가는 함수입니다.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f y [] = y
foldr f y (x:xs) = f x (foldr f y xs)
foldr
로 sumNumbers
함수 구현하기
foldr (+) 0 [1,2,3] ==> foldr (+) 0 (1:2:3:[])
==> 1 + (foldr (+) 0 (2:3:[]))
==> 1 + (2 + (foldr (+) 0 (3:[])))
==> 1 + (2 + (3 + (foldr (+) 0 [])))
==> 1 + (2 + (3 + 0))
foldr
로 map
함수 구현하기
map g xs = foldr helper [] xs
where helper y ys = g y : ys
타입들의 집합
이 타임들에 사용할 수 있는 동일한 이름의 함수가 있음
ghci> :type (+)
(+) :: Num a => a -> a -> a
a
안에 Num
이라는 타입 클래스에 속한 타입만 들어갈 수 있다.
ghci> :type (==)
(==) :: Eq a => a -> a -> Bool
a
안에 Eq
라는 타입 클래스에 속한 타입만 들어갈 수 있다.
다형성(Polymorphism)
Ex) a -> a
a
에 아무 조건 없는 다형성
parametric polymorphism
Ex) Num a => a -> a -> a
a
에 올 수 있는 타입은 Num
타입 클래스에 속함
ad-hoc polymorphism (overloading)
타입 클래스 함수를 사용할 때 구체적인 타입을 지정해줘야 할 수 도 있습니다.
addTrue :: Bool -> Bool
addTrue b = b + True
main.hs:2:15: error:
• No instance for (Num Bool) arising from a use of ‘+’
• In the expression: b + True
In an equation for ‘addTrue’: addTrue b = b + True
ghci> :type (+)
(+) :: Num a => a -> a -> a
+
연산을 하려면 기본적으로 Num
타입 클래스에 속해야 하는데, Bool
는 이에 속하지 않아서 오류가 발생했습니다.
f :: (a -> a) -> a -> Bool
f g x = x == g x
main.hs:2:11: error:
• No instance for (Eq a) arising from a use of ‘==’
Possible fix:
add (Eq a) to the context of
the type signature for:
f :: forall a. (a -> a) -> a -> Bool
• In the expression: x == g x
In an equation for ‘f’: f g x = x == g x
f
함수에서 인자의 타입을 a
로 주었는데, ==
연산을 진행하기 위해서는 타입이 Eq
클래스에 속해야 합니다.
그래서 오류가 발생하지 않게 하려면 인자의 타입을 더 구체적으로 지정해주어야 합니다.
a
타입이 Eq
클래스에 속한다고 선언
f :: Eq a => (a -> a) -> a -> Bool
f g x = x == g x
ghci> :reload
[1 of 1] Compiling Main ( main.hs, interpreted )
Ok, one module loaded.
만약 상세하게 타입을 지정해주는게 어렵다면?
ghci> f g x = x == g x
ghci> :type f
f :: Eq t => (t -> t) -> t -> Bool
함수를 정의하고 해당 함수의 타입을 출력해보면 됩니다.
==
, /=
연산자를 위한 타입을 제공
ghci> 1 == 2
False
ghci> 1.0 /= 2.0
True
ghci> "foo" == "bar"
False
ghci> [1,2] == [1,2]
True
ghci> [[1,2],[3,4]] /= [[1,2],[]]
True
Int, Double, String, List
모두 Eq
클래스에 속합니다.
ghci> (/x -> x+1) == (\x -> x+2)
<interactive>:50:5: error: parse error on input ‘->’
ghci> (\x -> x+1) == (\x -> x+2)
<interactive>:51:13: error:
• No instance for (Eq (Integer -> Integer))
arising from a use of ‘==’
(maybe you haven't applied a function to enough arguments?)
• In the expression: (\ x -> x + 1) == (\ x -> x + 2)
In an equation for ‘it’: it = (\ x -> x + 1) == (\ x -> x + 2)
람다 함수는 Eq
클래스에 포함되지 않습니다.
nub
함수 : 리스트에서 중복을 제거해줍니다.
ghci> import Data.List
ghci> :type nub
nub :: Eq a => [a] -> [a]
ghci> nub [3,5,3,1,1]
[3,5,1]
순서를 지을 수 있는 값들의 타입들을 포함하고 있습니다.
compare :: Ord a => a -> a -> Ordering
(<) :: Ord a => a -> a -> Bool
(>) :: Ord a => a -> a -> Bool
(>=) :: Ord a => a -> a -> Bool
(<=) :: Ord a => a -> a -> Bool
max :: Ord a => a -> a -> a
min :: Ord a => a -> a -> a
compare
함수 : 비교 결과에 따라 LT(less than), EQ(Equal), GT(greater than)
을 반환합니다.
ghci> compare 1 1
EQ
ghci> compare 1 3
LT
ghci> compare 4 2
GT
ghci> compare 'a' 'z'
LT
ghci> max 10 5
10
ghci> max "abc" "wb"
"wb"
ghci> min 5 3
3
ghci> min [1,2,3] [4,5,6]
[1,2,3]
import Data.List
-- from the module Data.Ord
-- compares two values "through" the function f
comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering
comparing f x y = compare (f x) (f y)
-- from the module Data.List
-- sorts a list using the given comparison function
-- sortBy :: (a -> a -> Ordering) -> [a] -> [a]
-- sorts lists by their length
sortByLength :: [[a]] -> [[a]]
sortByLength = sortBy (comparing length)
ghci> sortByLength [[1,2,3],[4,5],[4,5,6,7]]
[[4,5],[1,2,3],[4,5,6,7]]
sortByLength
함수 : 리스트 내부의 리스트들을 길이에 따라 정렬해줍니다.
Num
클래스 : 산술 연산을 포함하는 클래스
(+) :: Num a => a -> a -> a
(-) :: Num a => a -> a -> a
(*) :: Num a => a -> a -> a
negate :: Num a => a -> a -- 0-x
abs :: Num a => a -> a -- absolute value
signum :: Num a => a -> a -- -1 for negative values, 0 for 0, +1 for positive values
fromInteger :: Num a => Integer -> a
ghci> :type 123
123 :: Num a => a
ghci> 123 :: Int
123
ghci> 123 :: Double
123.0
123
의 타입을 출력해보면 Num
으로 나오는데, 그 이유는 123
이 Num
클래스에 있는 Int, Double, Float
등 여러 타입으로 쓰일 수 있기 때문입니다.
Integral
클래스 : Int
나 Integer
같은 숫자들을 표현하는 타입들
div :: Integral a => a -> a -> a
mod :: Integral a => a -> a -> a
Integral
에 속하는 타입들은 Num
에도 속합니다.
Fractional
클래스 : /
연산을 할 수 있는 타입
(/) :: Fractional a => a -> a -> a
Floating
클래스 : 실수를 다루는 타입들
sqrt :: Floating a => a -> a
sin :: Floating a => a -> a
Floating
타입에 속하는 타입들은 Fractional
에도 속합니다.
show :: Show a => a -> String
read :: Read a => String -> a
show
: 다른 타입의 값을 String
타입으로 변환합니다.
read
: 문자열 타입의 값을 다른 타입으로 변환합니다.
ghci> show 5
"5"
ghci> read "5" :: Int
5
ghci> read "5" :: Double
5.0
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Foldable
클래스 : fold할 수 있는 타입을 모아 놓은 것
Ex) list
, Maybe
ghci> :type length
length :: Foldable t => t a -> Int
ghci> length [1,2,3]
3
ghci> length (Just 1)
1
ghci> length Nothing
0
foldr (+) 1 Nothing ==> 1
foldr (+) 1 (Just 3) ==> 4
length Nothing ==> 0
length (Just 'a') ==> 1
타입 클래스에서 공통적으로 사용할 수 있는 이름이 궁금하면?
ghci> :info Num
type Num :: * -> Constraint
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
-- Defined in ‘GHC.Num’
instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’
:info [Type Class]
로 검색 가능
key-value를 검색하는 작업의 경우 리스트 보다 맵을 사용하는게 더 효율적입니다.
import qualified Data.Map as Map
Map
을 import 합니다.
ghci> values = Map.fromList [("z",3),("w",4)]
ghci> :type values
values :: Num a => Map.Map String a
Map.fromList
: 리스트를 받아서 맵을 만들어줍니다.
ghci> Map.insert "x" 7 values
fromList [("w",4),("x",7),("z",3)]
Map.insert
: 맵에 새로운 key-value를 삽입한 결과를 반환합니다.
원래 values
의 값이 변하지는 않습니다. (불변성)
ghci> values
fromList [("w",4),("z",3)]
ghci> Map.lookup "z" values
Just 3
ghci> Map.lookup "banana" values
Nothing
Map.lookup
: 주어진 key에 해당하는 value값을 찾아 리턴합니다.
ghci> values = Map.empty
ghci> values
fromList []
Map.empty
: 비어있는 맵을 만듭니다.
위 함수들의 타입을 봐보면
ghci> :type Map.fromList
Map.fromList :: Ord k => [(k, a)] -> Map.Map k a
(key, value)
로 이루어진 리스트를 받아서 맵을 만듭니다.
ghci> :type Map.lookup
Map.lookup :: Ord k => k -> Map.Map k a -> Maybe a
key
와 Map
을 받는데 여기서 value
값을 Maybe
타입으로 리턴합니다.
ghci> :type Map.insert
Map.insert :: Ord k => k -> a -> Map.Map k a -> Map.Map k a
key
, value
, Map
을 받아서 해당 key-value
가 포함된 맵을 리턴합니다.
ghci> :type Map.empty
Map.empty :: Map.Map k a
비어있는 맵을 리턴합니다.
withdraw
함수 : 은행 계좌에서 돈을 인출하는 함수
import qualified Data.Map as Map
withdraw :: String -> Int -> Map.Map String Int -> Map.Map String Int
withdraw account amount bank =
case Map.lookup account bank of
Nothing -> bank -- account not found, no change
Just sum -> Map.insert account (sum-amount) bank -- set new balance
ghci> bank = Map.fromList [("Bob",100),("Mike",50)]
ghci> withdraw "Bob" 80 bank
fromList [("Bob",20),("Mike",50)]
ghci> bank
fromList [("Bob",100),("Mike",50)]
Bob의 계좌에서 80을 인출합니다.
함수 실행 후 원래 bank
맵의 값은 변하지 않습니다.
fromList [("Bob",20),("Mike",50)]
ghci> withdraw "Bozo" 1000 bank1
fromList [("Bob",20),("Mike",50)]
없는 사용자로 인출을 하면 맵의 값이 변하지 않습니다.
withdraw
함수의 내부적인 동작
ghci> Map.lookup "Bob" bank
Just 100
먼저 해당 사용자가 존재하는지 맵에 검색을 합니다.
ghci> Map.insert "Bob" (100-80) bank
fromList [("Bob",20),("Mike",50)]
그 후 해당 사용자의 value값에서 인자로 들어온 값을 뺍니다.
Array
타입 import
import Data.Array
ghci> :type array
array :: Ix i => (i, i) -> [(i, e)] -> Array i e
하스켈에서 배열의 인덱스에는 Int
타입 뿐만 아니라 Ix
클래스에 해당하는 다른 여러 타입들도 올 수 있습니다.
ghci> arrInt = listArray (7,11) ["seven", "eight", "nine", "ten", "eleven"]
ghci> arrInt
array (7,11) [(7,"seven"),(8,"eight"),(9,"nine"),(10,"ten"),(11,"eleven")]
listArray
: 인덱스와 리스트를 받아 배열을 만들어줍니다.
ghci> arrInt ! 7
"seven"
ghci> arrInt ! 8
"eight"
ghci> arrInt ! 20
"*** Exception: Ix{Integer}.index: Index (20) out of range ((7,11))
!
: 인덱스에 해당하는 값을 리턴해줍니다.
ghci> array (7,11) [(11,"eleven"),(10,"ten"),(9,"nine"),(8,"eight"),(7,"seven")]
array (7,11) [(7,"seven"),(8,"eight"),(9,"nine"),(10,"ten"),(11,"eleven")]
array
: 정렬되지 않은 리스트를 정렬 시켜서 배열로 만들어줍니다.
ghci> arrInt
array (7,11) [(7,"seven"),(8,"eight"),(9,"nine"),(10,"ten"),(11,"eleven")]
ghci> arr = arrInt // [(8,"EIGHT")]
ghci> arr
array (7,11) [(7,"seven"),(8,"EIGHT"),(9,"nine"),(10,"ten"),(11,"eleven")]
//
: 해당하는 인덱스의 값을 업데이트한 배열을 리턴합니다.