repeatString n str = repeatHelper n str ""
repeatHelper n str result = if (n==0)
then result
else repeatHelper (n-1) str (result++str)
Pattern Matching
--fibonacci numbers, slow version
fibonacci 1 = 1
fibonacci 2 = 1
fibonacci n = fibonacci(n-2) + fibonacci(n-1)
ex) fibonacci 5
==> fibonacci 3 + fibonacci 4
==> (fibonacci 1 + fibonacci 2) + fibonacci 4
==> (1+1) + fibonacci 4
==> (1+1) + (fibonacci 2 + fibonacci 3)
==> (1+1) + (fibonacci 2 + (fibonacci 1 + fibonacci 2))
==> (1+1) + (1+(1+1))
==> 5
Helper Functions
--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)
ex) fibonacci 5
==> fibonacci' 0 1 5
==> fibonacci' 1 1 4
==> fibonacci' 1 2 3
==> fibonacci' 2 3 2
==> fibonacci' 3 5 1
==> 5
--definition--
f x y z
| condition 1 = something
| condition 2 = other
| otherwise = somethingother
🧷 Guard와 Pattern Matching의 차이점
: Guard를 사용하면 임의의 코드를 그룹화하여 작성 가능,
Pattern matching은 패턴에 따라서 쭉 나열
--pattern matching
factorial :: Int -> Int
factorial 1 = 1
facotirla n = n * factorial(n-1)
--guards
factorial n
| n<0 = -1
| n==0 = 1
| otherwise = n * factorial(n-1)
🧷 Guard와 Pattern Matching 조합(combine) 가능
guessAge :: String -> Int -> String
guessAge "Griselda" age --pattern matching
| 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> [0,3,4,1+1] :: [Int]
[0,3,4,2]
ghci> :t [True,True,False]
[True,True,False] :: [Bool]
ghci> :type ["Moi","Hei"]
["Moi","Hei"] :: [String]
ghci> :t []
[] :: [a] --a는 타입변수
ghci> :t [[1,2],[3,4]] :: [[Int]]
[[1,2],[3,4]] :: [[Int]] :: [[Int]]
ghci> [1..7] :: [Int]
[1,2,3,4,5,6,7]
ghci> :t [1..7] :: [Int]
[1..7] :: [Int] :: [Int]
List Operations
head :: [a] -> a -- returns the first element
tail :: [a] -> [a] -- returns everything except the first element
init :: [a] -> [a] -- returns evething except the last element
last :: [a] -> a --returns the last element
take :: Int -> [a] -> [a] -- returns the n first elements
drop :: Int -> [a] -> [a] -- returns everything except the n first elements
(++) :: [a] -> [a] -> [a] -- lists are catenated with the ++ operator
(!!) :: [a] -> Int -> a -- lists are indexed with the !! operator
reverse :: [a] -> [a] -- reverse a list
null :: [a] -> Bool -- is this list empty?
length :: [a] -> Int -- the length of a list
ghci> head [1,2,3,4,5]
1
ghci> tail [1,2,3,4,5]
[2,3,4,5]
ghci> init [1,2,3,4,5]
[1,2,3,4]
ghci> last [1,2,3,4,5]
5
ghci> take 3 [1,2,3,4,5]
[1,2,3]
ghci> take 100 [1,2,3,4,5]
[1,2,3,4,5]
ghci> drop 3 [1,2,3,4,5]
[4,5]
ghci> drop 100 [1,2,3,4,5]
[]
ghci> (++) [1,2,3] [4,5]
[1,2,3,4,5]
ghci> (!!) [1,2,3,4,5] 3
4
ghci> reverse [1,2,3,4,5]
[5,4,3,2,1]
ghci> null [1,2,3,4,5]
False
ghci> null []
True
ghci> length [1,2,3,4,5]
5
ghci> [1,2,3] == [1,2,3]
True
ghci> [1,2,3] == [3,1,2]
False
ghci> :type "asdf"
"asdf" :: String
ghci> :type "asdf" :: [Char]
"asdf" :: [Char] :: [Char]
Data.list
module 사용ghci> import Data.List
ghci> sort [5,3,2,4,1]
[1,2,3,4,5]
ghci> [7,10,4,5] !! 2
4
ghci> f xs = take 2 xs ++ drop 4 xs
ghci> f [1,2,3,4,5,6]
[1,2,5,6]
ghci> f [1,2,3]
[1,2]
ghci> g xs = tail xs ++ [head xs]
--head는 원소 하나를, tail은 list를 리턴하기 때문에 연산이 불가하여 타입을 맞춰준다.
ghci> g [1,2,3]
[2,3,1]
ghci> it --제일 마지막 결과를 가리킴
[2,3,1]
ghci> g it
[3,1,2]
ghci> list = [1,2,3,4]
ghci> reverse list
[4,3,2,1]
ghci> list
[1,2,3,4]
ghci> drop 2 list
[3,4]
ghci> list
[1,2,3,4]
-> list의 값은 변하지 않는다.t
head :: [a] -> a
a : 타입 변수 (e.g. b,c,thisIsATypeVariable 모두 가능 -> 첫 글자 소문자)
ghci> :type [True,False]
[True,False] :: [Bool]
ghci> :type head
head :: [a] -> a
ghci> :type head [True,False]
head [True,False] :: Bool
ghci> :type tail
tail :: [a] -> [a]
ghci> tail [True,False]
[False]
ghci> :type tail [True,False]
tail [True,False] :: [Bool]
ghci> f xs ys = [head xs, head ys]
ghci> :type head
head :: [a] -> a
ghci> :type f
f :: [a] -> [a] -> [a]
ghci> g zs = f "Moi" zs
ghci> :type g
g :: [Char] -> [Char] --"Moi"가 [Char]이므로 두번째 리스트도 [Char]
용어
type parameter : 리스트 안에 들어오는 parameter로 지정한 타입
parameterized type : parameter를 받는(가지고 있는) 타입
polymorphism : 여러 종류 타입의 인자를 가질 수 있는 특성
type variable : 타입 변수
parametric polymorphism : 타입 변수를 사용하는 형태
Type Annotations (타입 주석)
ex) guessAge :: String -> Int -> String
타입 주석을 주는 것을 권장
Maybe
타입의 constructor(생성자) : Nothing
, Just
함수 호출 시, 정상적인 경우와 비정상적인 경우 합쳐진 것을 리턴값으로 하고자 할 때, Maybe 타입 사용!
-> 정상적인 경우 Just a 리턴, 비정상적인 경우 Nothing
ghci> :t Nothing
Nothing :: Maybe a
ghci> :t Just
Just :: a -> Maybe a
ghci> Nothing :: Maybe Bool
Nothing
ghci> Just False :: Maybe Bool
Just False
ghci> Just True :: Maybe Bool
Just True
ghci> Nothing :: Maybe Int
Nothing
ghci> Just 0 :: Maybe Int
Just 0
ghci> Just 1 :: Maybe Int
Just 1
ghci> Just (-1) :: Maybe Int
Just (-1)
ghci> Nothing :: Maybe [Int]
Nothing
ghci> Just [] :: Maybe [Int]
Just []
ghci> Just [1,2] :: Maybe [Int]
Just [1,2]
ghci> Just "a camel"
Just "a camel"
ghci> :t Just "a camel"
Just "a camel" :: Maybe String
ghci> Just True
Just True
ghci> :t Just True
Just True :: Maybe Bool
login :: String -> Maybe String
login "f4bulous!" = Just "unicon73"
login "swordfish" = Just "megahacker"
login _ = Nothing
ghci> login "nang"
Nothing
ghci> login "swordfish"
Just "megahacker"
perhapsMultiply :: Int -> Maybe Int -> Int
perhapsMultiply i Nothing = i
perhapsMultiply i (Just j) = i*j
ghci> perhapsMultiply 3 (Just 2)
6
ghci> perhapsMultiply 3 Nothing
3
intOrZero :: Maybe Int -> Int
intOrZero Nothing = 0
intOrZero (Just i) = i
safeHead :: [a] -> Maybe a
safeHead xs = if null xs then Nothing else Just (head xs)
--head 함수는 빈 리스트가 인자로 오면 에러 발생
--safeHead 함수는 빈 리스트가 오면 Nothing 리턴
headOrZero :: [Int] -> Int
headOrZero xs = intOrZero (safeHead xs)
ghci> intOrZero (Just 5)
5
ghci> intOrZero Nothing
0
ghci> safeHead [1,2,3,4,5]
Just 1
ghci> safeHead []
Nothing
ghci> headOrZero []
0
ghci> headOrZero [1,2,3,4,5]
1
headOrZero [] => intOrZero (safeHead []) => intOrZero Nothing => 0
headOrZero [1] => intOrZero (safeHead [1]) => intOrZero (Just 1) => 1
Maybe 타입의 두가지 생성자 : Just
, Nothing
대문자로 시작
pattern matching 을 할 수 있다.
Nothing, True, False는 constructors 이면서 따로 인자를 받지 않으므로 constants (상수) 취급
ghci> :t Nothing
Nothing :: Maybe a
ghci> :t True
True :: Bool
ghci> :t False
False :: Bool
ghci> :t Just
Just :: a -> Maybe a
Left
, Right
ghci> Left 0 :: Either Int Bool
Left 0
ghci> Right True :: Either Int Bool
Right True
ghci> Left "asdf" :: Either String [Int]
Left "asdf"
ghci> Right [1,2] :: Either String [Int]
Right [1,2]
readInt :: String -> Either String Int
readInt "0" = Right 0
readInt "1" = Right 1
readInt s = Left ("Unspported string: " ++ s)
ghci> readInt "0"
Right 0
ghci> readInt "1"
Right 1
ghci> readInt "5"
Left "Unspported string: 5"
iWantAString :: Either Int String -> String
iWantAString (Right str) = str
iWantAString (Left number) = show number
ghci> iWantAString (Right "abcd")
"abcd"
ghci> iWantAString (Left 5)
"5"
ghci> :type [Left 1, Right "foo", Left 2] :: [Either Int String]
[Left 1, Right "foo", Left 2] :: [Either Int String]
:: [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
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"
ghci> flyTo "FI"
"You're flying to Finland"
ghci> flyTo "DE"
"You're not flying anywhere"
sentenceType :: String -> String
sentenceType sentence =
case last sentence of '.' -> "statement"
'?' -> "question"
'!' -> "exclamation"
_ -> "not a sentence"
sentenceType1 :: String -> String
sentenceType1 sentence = classify (last sentence)
where classify '.' = "statement"
classify '?' = "question"
classify '!' = "exclamation"
classify _ = "not a sentence"
ghci> sentenceType "This is Haskell."
"statement"
ghci> sentenceType1 "This is Haskell."
"statement"
🤔 언제 case-of 을 사용하나요?
1) 함수 호출 후 결과에 대해 pattern matching을 할 때
motivate :: String -> String
motivate "Monday" = "Have a nice week at work!"
motivate "Tuesday" = "You're one day closer to weekend!"
motivate "Wednesday" = "3 more days until the weekend!"
motivate "Thursday" = "2 more days until the weekend!"
motivate "Friday" = "1 more day 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!"
motivate :: String -> String
motivate day
| n == 6 = "Have a nice week at work!"
| n == 5 = "You're one day closer to weekend!"
| n > 1 = show(n-1) ++ " more day(s) until weekend!"
| otherwise = "Relax! You don't need to work today!"
where n = distanceToSunday day
2) helper function을 여러 패턴 간 공유할 경우
area :: String -> Double -> Double
area "square" x = square x
area "circle" x = pi * square x
where square x = x * x
area :: String -> Double -> Double
area shape x = case shape of
"square" -> square x
"circle" -> pi * square x
where square x = x * x
3) 함수 이름을 여러 번 사용해야 하는 경우
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
f :: Bool -> Maybe Int -> Int
f False Nothing = 1
f False _ = 2
f True (Just i) = i
f True Nothing = 0
case number of 0 -> "zero"
1 -> "one"
_ -> "not zero or one"
getElement :: Maybe Int -> [a] -> a
getElement (Just i) xs = xs !! i
getElement Nothing xs = last xs
ghci> getElement (Just 3) [1,2,3,4,5,6]
4
ghci> getElement Nothing [1,2,3,4,5,6]
6
x==Nothing
과 같은 연산자를 항상 사용할 수 있는 것은 아님In Haskell a function is a value
applyTo1 :: (Int -> Int) -> Int // 괄호 주의!
applyTo1 f = f 1
addThree :: Int -> Int
addThree x = x + 3
{-
applyTo1 addThree
==> addThree 1
==> 1 + 3
==> 4
-}
doTwice :: (a -> a) -> a -> a --첫번째 인자로 함수 받음 -> 고차원함수
doTwice f x = f (f x)
{-
doTwice addThree a -> a = Int -> Int, a = Int
doTwice :: (Int -> Int) -> Int
==> addThree (addThree 1)
==> addThree 4
==> 7
-}
makeCool :: String -> String
makeCool str = "WOW " ++ str ++ "!"
{-
doTwice makeCool "Haskell"
a -> a = String -> String, a = String
a = String , a = String
==> makeCool (makeCool "Haskell")
==> makeCool "WOW Haskell!"
==> "WOW WOW Haskell!!"
-}
Functional Programming on Lists
1) map
map :: (a -> b) -> [a] -> [b]
ghci> map addThree [1,2,3]
[4,5,6]
{-
map :: (a -> b) -> [a] -> [b]
addThree :: Int -> Int
map addThree [1,2,3]
a -> b = Int -> Int a=Int, b=Int
[a] = [Int] a = Int
map :: (Int -> Int) -> [Int] -> [Int]
map addThree :: [Int] -> [Int]
-}
2) filter
filter :: (a -> Bool) -> [a] -> [a]
positive :: Int -> Bool
positive x = x > 0
ghci> filter positive [0,1,-1,3,-3]
[1,3]
{-
filter :: (a -> Bool) -> [a] -> [a]
filter positive [0,1,-1,3,-3]
a -> Bool = Int -> Bool a = Int
[a] = [Int] a = Int
filter :: (Int -> Bool) -> [Int] -> [Int]
filter positive :: [Int] -> [Int]
-}
onlyPositive xs = filter positive xs
mapBooleans f = map f [False, True]
{-
filter :: (a -> Bool) -> [a] -> [a]
positive :: Int -> Bool a = Int
onlyPositive :: [Int] -> [Int]
-}
{-
map :: (a -> b) -> [a] -> [b]
[a] = [Bool] a = Bool
map :: (Bool -> b) -> [Bool] -> [b]
mapBooleans :: (Bool -> b) -> [b]
-}
{-
mapBooleans :: (Bool -> b) -> [b]
not :: Bool -> Bool
mapBooleans not :: [Bool]
Bool -> b = Bool -> Bool b = Bool
-}
wrapJust xs = map Just xs
{-
map :: (a -> b) -> [a] -> [b]
Just :: a -> Maybe a
map Just xs
a -> b = a -> Maybe a b = Maybe a
map :: (a -> Maybe a) -> [a] -> [Maybe a]
wrapJust :: [a] -> [Maybe a]
-}
리스트 함수형 프로그래밍 예제
palindrome :: String -> Bool
palindrome str = str == reverse str
palindromes :: Int -> [String]
palindromes n = filter palindrome (map show [1..n])
ghci> 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"]
countAwords :: String -> Int
countAwords string = length (filter startsWithA (words string))
--words :: String -> [String]
where startsWithA s = head s == 'a'
ghci> countAwords "does anyone want an apple?"
3
import Data.List
tails :: [a] -> [[a]]
ghci> tails "echo"
["echo","cho","ho","o",""]
substringOfLength :: Int -> String -> [String]
substringOfLength n string = map shorten (tails string)
where shorten s = take n s
ghci> substringOfLength 3 "hello"
["hel","ell","llo","lo","o",""]
whatFollows :: Char -> Int -> String -> [String]
whatFollows c k string =
map tail
(filter match (substringOfLength (k+1) string))
where match sub = take 1 sub == [c]
ghci> whatFollows 'a' 2 "abracadabra"
["br","ca","da","br",""]
: 함수의 인자를 부분적으로 받아 함수 호출
ghci> add a b = a + b
ghci> add 1 5
6
ghci> addThree = add 3
ghci> addThree 2
5
ghci> map addThree [1,2,3]
[4,5,6]
ghci> map (add 3) [1,2,3]
[4,5,6]
between :: Integer -> Integer -> Integer -> Bool
between low high x = x < high && x > low
ghci> between 3 7 5
True
ghci> between 3 6 9
False
ghci> (between 1 5) 2
True
ghci> let f = between 1 5 in f 2
True
ghci> map (between 1 3) [1,2,3]
[False,True,False]
cyrrying
: partial application을 가능하게 하는 방식
Integer -> Integer -> Integer -> Bool
= Integer -> (Integer -> Integer -> Bool)
= Integer -> ( Integer -> (Integer -> Bool) )
ghci> :t between 1 2 3
between 1 2 3 :: Bool
ghci> :t between 1 2
between 1 2 :: Integer -> Bool
ghci> :t between 1
between 1 :: Integer -> Integer -> Bool
ghci> :t between
between :: Integer -> Integer -> Integer -> Bool
sections
ghci> map (*2) [1,3,5,7,9]
[2,6,10,14,18]
ghci> map (+3) [1,3,5,7,9]
[4,6,8,10,12]
ghci> map (1/) [1,3,5,7,9]
[1.0,0.3333333333333333,0.2,0.14285714285714285,0.1111111111111111]
ghci> map (/2) [1,3,5,7,9]
[0.5,1.5,2.5,3.5,4.5]
ghci> :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
ghci> zipWith (+) [0,2,5] [1,3,3]
[1,5,8]
ghci> 6 `div` 2
3
ghci> div 6 2
3
ghci> (+1) `map` [1,2,3]
[2,3,4]
let big x = x > 7 in filter big [1,10,100]
🟰
filter (\x -> x > 7) [1,10,100]
ghci> (\x -> x*x) 3
9
ghci> (\x-> reverse x == x) "ABBA"
True
ghci> filter (\x->reverse x == x) ["ABBA","ACDC","otto","lothar","anna"]
["ABBA","otto","anna"]
ghci> (\x y -> x^2 + y^2) 2 3
13
(.) : 함수 조합
($) : 괄호 줄이기
=> 고차원 함수를 작성할 때 유용
1) (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
(f.g) x ==> f (g x)
double x = 2 * x
quadruple = double . double
f = quadruple . (+1)
g = (+1) . quadruple
third = head . tail. tail
doTwice f = f.f
let ttail = doTwice tail
ghci> ttail [1,2,3,4]
[3,4]
ghci> (doTwice tail) [1,2,3,4]
[3,4]
ghci> notEmpty x = not (null x)
ghci> filter notEmpty [[1,2,3],[],[4]]
[[1,2,3],[4]]
🟰
ghci> filter (not . null) [[1,2,3],[],[4]]
[[1,2,3],[4]]
2) ($)
($) :: (a -> b) -> a -> b
ghci> head (reverse "abcd")
'd'
🟰
ghci> head $ reverse "abcd"
'd'
ghci> reverse (map head (map reverse (["Haskell","pro"] ++ ["dodo","lyric"])))
"cool"
ghci> (reverse . map head . map reverse) (["Haskell","pro"] ++ ["dodo","lyric"])
"cool"
ghci> reverse . map head . map reverse $ ["Haskell","pro"] ++ ["dodo","lyric"]
"cool"
map ($ "string") [reverse, take 2, drop 2]
==> [reverse $ "string", take 2 "string", drop 2 "string"]
==> ["gnirts","st","ring"]
takeWhile even [2,4,1,2,3] ==> [2,4]
dropWhile even [2,5,1,2,4] ==> [5,1,2,4]
--리스트 포함 여부
elem 3 [1,2,3] ==> True
elem 4 [1,2,3] ==> False
findSubstring :: String -> String -> String
findSubstring chars = takeWhile (\x -> elem x chars)
. dropWhile (\x -> not $ elem x chars)
ghci> findSubstring "a" "bbaabaaaab"
"aa"
ghci> findSubstring "abcd" "xxxyyyzabaaxxabcd"
"abaa"
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (++) ["John", "Mary"]["Smith","Cooper"]
==> ["JohnSmith","MaryCooper"]
zipWith take [4,3] ["Hello","Warden"]
==> ["Hell","War"]
id :: a -> a
filter id [True,False,True,True]
==> [True,True,True]
dropWhile id [True,True,False,True,False]
==> [False,True,False]
ghci> :t (:)
(:) :: a -> [a] -> [a]
ghci> [1]
[1]
ghci> 1 : []
[1]
ghci> 1 : [2,3]
[1,2,3]
ghci> tail (1 : [2,3])
[2,3]
ghci> head (1 : [2,3])
1
리스트 만들기
descend 0 = []
descend n = n : descend (n-1)
ghci> descend 4
[4,3,2,1]
let xs = "terve"
in _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
ghci> split 'x' "fooxxbarxquux"
["foo","","bar","quu"]
리스트 패턴매칭
myhead :: [Int] -> Int
myhead [] = -1
myhead (first : rest) = first
mytail :: [Int] -> [Int]
mytail [] = []
mytail (first : rest) = rest
ghci> myhead [3,5,7,9]
3
ghci> mytail [1,2,3,4,5]
[2,3,4,5]
sumFirstTwo :: [Integer] -> Integer
sumFirstTwo (a:b:_) = a+b
sumFirstTwo _ = 0
ghci> sumFirstTwo []
0
ghci> sumFirstTwo [1]
0
ghci> sumFirstTwo [1,2]
3
ghci> sumFirstTwo [1,2,4]
3
describeList :: [Int] -> String
describeList (x:y:z:xs) = "a list with at least three elements"
describeList (x:y:[]) = "a list with two elements"
describeList (x:[]) = "a list with one element"
describeList [] = "an empty list"
ghci> describeList [1,3]
"a list with two elements"
ghci> describeList [1,2,3,4,5]
"a list with at least three elements"
ghci> describeList []
"an empty list"
❗️ x : y: z : [] = [x,y,z]
startsWithZero :: [Integer] -> Bool
startsWithZero (0:xs) = True
startsWithZero (1:xs) = False
startsWithZero [] = False
ghci> startsWithZero [1,3,5]
False
ghci> startsWithZero []
False
ghci> startsWithZero [0,1,2]
True
리스트 사용하기
sumNumbers :: [Int] -> Int
sumNumbers [] = 0
sumNumbers (x : xs) = x + sumNumbers xs
ghci> sumNumbers [1,2,3]
6
--myMaximum의 인자는 하나이므로 재귀함수 방식으로 반복할 수는 없음 => 로컬함수 go 사용 (helper function)
myMaximum :: [Int] -> Int
myMaximum [] = 0
myMaximum (x : xs) = go x xs
where go biggest [] = biggest
go biggest (x:xs) = go (max biggest x) xs
ghci> myMaximum [-5,3,100,99,9999]
9999
countNothings :: [Maybe a] -> Int
countNothings [] = 0
countNothings (Nothing : xs ) = 1 + countNothings xs
countNothings (Just _ : xs) = countNothings xs
ghci> 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]
map :: (a->b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
filter :: (a->Bool) -> [a] -> [a]
filter _pred [] = []
filter pred (x:xs)
| pred x = x : filter pred xs
| otherwise = filter pred xs
꼬리 재귀와 리스트
-- Not tail recursive
sumNumbers :: [Int] -> Int
sumNumbers [] = 0
sumNumbers (x:xs) = x + sumNumbers xs
-- Tail recursive version
sumNumbers :: [Int] -> Int
sumNumbers xs = go 0 xs
where go sum [] = sum
go sum (x:xs) = go (sum+x) xs
ghci> sumNumbers [1,3,5,7,9]
25
ghci> [2*i | i <- [1,2,3]]
[2,4,6]
ghci> map (2*) [1,2,3]
[2,4,6]
ghci> [i| i<- [1..7], even i]
[2,4,6]
ghci> filter even [1..7]
[2,4,6]
ghci> [2*i | i<-[1..7],even i]
[4,8,12]
ghci> map (2*) (filter even [1..7])
[4,8,12]
[first ++ " " ++ last | first <- ["John", "Mary"], last <- ["Smith", "Cooper"]]
==> ["John Smith","John Cooper","Mary Smith","Mary Cooper"]
[reversed | word <- ["this","is","a","string"], let reversed = reverse word]
==> ["siht","si","a","gnirts"]
🟰
[reverse word | word <- ["this","is","a","string"]]
==> ["siht","si","a","gnirts"]
firstLetters string = [char | (char:_) <- words string]
ghci> firstLetters "Hello World!"
"HW"
(<+>) :: [Int] -> [Int] -> [Int]
xs <+> ys = zipWith (+) xs ys
ghci> [1,2,3] <+> [4,5,6]
[5,7,9]
ghci> zipWith (+) [1,2,3] [4,5,6]
[5,7,9]
(+++) :: String -> String -> String
a +++ b = a ++ " " ++ b
ghci> (+++) "Hello" "World!"
"Hello World!"
ghci> "Hello" ++ " " ++ "World!"
"Hello World!"
fst :: (a, b) -> a
snd :: (a, b) -> b
zip :: [a] -> [b] -> [(a, b)]
unzip :: [(a, b)] -> ([a], [b])
partition :: (a -> Bool) -> [a] -> ([a], [a])
ghci> zip [1,2,3] [True,False,True]
[(1,True),(2,False),(3,True)]
ghci> unzip [("Fred",1),("Jack",10),("Helon",13)]
(["Fred","Jack","Helon"],[1,10,13])
ghci> partition (>0) [-1,1,-4,3,2,0]
([1,3,2],[-1,-4,0])
swap :: (a,b) -> (b,a)
swap (x,y) = (y,x)
ghci> swap (1, True)
(True,1)
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
위 세 함수의 공통점
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f y [] = y
foldr f y (x:xs) = f x (foldr f y xs)
--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))
map g xs = foldr helper [] xs
where helper y ys = g y : ys
다형성 (polymorphism)
f :: (Eq a) => (a -> a) -> a -> Bool
f g x = x == g x
bothPairsEqual :: (Eq a, Eq b) => a -> a -> b -> b -> Bool
bothPairsEqual left1 left2 right1 right2 = left1 == left2 && right1 == right2
Eq
(==) :: Eq a => a -> a -> Bool
(/=) :: Eq a => a -> a -> Bool
ghci> import Data.List
ghci> :t nub -- 중복 제거
nub :: Eq a => [a] -> [a]
ghci> nub [3,5,3,1,1]
[3,5,1]
ghci> nub [[1,2],[3],[2,1],[1,2]]
[[1,2],[3],[2,1]]
Ord
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
ghci> compare 1 1
EQ --Equal
ghci> compare 1 3
LT --Less Than
ghci> compare 1 0
GT --Greater Than
ghci> compare 'a' 'z'
LT
ghci> compare 'b' 'B'
GT
ghci> min 5 3
3
ghci> max 5 3
5
ghci> "nang" > "dang"
True
ghci> :t EQ
EQ :: Ordering
ghci> [1,2,3] > [2,5] -- 맨 첫 원소부터 비교 (string과 동일한 방식)
False
ghci> [1,2,3] < [1,2,4]
True
import Data.List
sort :: Ord a => [a] -> [a]
ghci> sort [6,1,4,8,2]
[1,2,4,6,8]
ghci> sort "black sphinx of quartz, judge my vow!"
" !,aabcdefghijklmnoopqrstuuvwxyz"
ghci> ' ' < 'a'
True
ghci> '!' < 'a'
True
import Data.List
comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering
comparing f x y = compare (f x) (f y)
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]]
Num, Integral, Factional, Floating
(+) :: Num a => a -> a -> a
(-) :: Num a => a -> a -> a
(*) :: Num a => a -> a -> a
negate :: Num a => a -> a -- 음수로 바꿈
abs :: Num a => a -> a -- 절댓값
signum :: Num a => a -> a -- 부호 구하기 (-1, 0, +1 리턴)
fromInteger :: Num a => Integer -> a
(+) :: Num a => a -> a -> a
ghci> :t 123
123 :: Num p => p
ghci> 123 :: Double
123.0
ghci> 123 :: Int
123
ghci> 123 :: Integer
123
ghci> 123 :: Float
123.0
fromIntegral :: (Integral a, Num b) => a -> b
ghci> fromIntegral 3
3
ghci> fromIntegral 3 :: Double
3.0
div :: Integral a => a -> a -> a
mod :: Integral a => a -> a -> a
ghci> :t 123 `div` 5
123 `div` 5 :: Integral a => a
ghci> :t (123 `div` 5) :: Int
(123 `div` 5) :: Int :: Int
ghci> :t (123 `div` 5) :: Integer
(123 `div` 5) :: Integer :: Integer
(/) :: Fractional a => a -> a -> a
ghci> :t 123 / 5
123 / 5 :: Fractional a => a
ghci> :t 123 `div` 5
123 `div` 5 :: Integral a => a
sqrt :: Floating a => a -> a
sin :: Floating a => a -> a
ghci> :t (sqrt 2) :: Double
(sqrt 2) :: Double :: Double
ghci> :t (sqrt 2) :: Float
(sqrt 2) :: Float :: Float
Read and Show
show :: Show a => a -> String
read :: Read a => String -> a
ghci> show 3
"3"
ghci> read "3" :: Int --타입 지정 해줘야함 (문자열을 읽어서 어떤 타입으로 바꿀지)
3
ghci> read "3" :: Double
3.0
ghci> show [1,2,3]
"[1,2,3]"
ghci> read "[1,2,3]" :: [Int]
[1,2,3]
Foldable
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
length :: Foldable t => t a -> Int
foldr (+) 1 Nothing ==> 1
foldr (+) 1 (Just 3) ==> 4
length Nothing ==> 0
length (Just 'a') ==> 1
🤔 타입클래스에 속한 연산자, 함수를 알아내고 싶을 때는 어떻게 하나요?
:info (타입클래스)
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’
Data.Map
import qualified Data.Map as Map
ghci> values = Map.fromList [("z",3),("w",4)]
ghci> values
fromList [("w",4),("z",3)]
ghci> Map.lookup "z" values
Just 3
ghci> Map.lookup "banana" values
Nothing
ghci> Map.insert "x" 7 values
fromList [("w",4),("x",7),("z",3)]
ghci> values --immutability
fromList [("w",4),("z",3)]
ghci> Map.insert "x" 1 (Map.insert "y" 2 values)
fromList [("w",4),("x",1),("y",2),("z",3)]
ghci> values
fromList [("w",4),("z",3)]
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> bank
fromList [("Bob",100),("Mike",50)]
ghci> withdraw "Bob" 80 bank
fromList [("Bob",20),("Mike",50)]
ghci> bank
fromList [("Bob",100),("Mike",50)]
ghci> withdraw "Bozo" 1000 bank
fromList [("Bob",100),("Mike",50)]
Data.Array
import Data.Array
array :: Ix i => (i, i) -> [(i, e)] -> Array i e
-- Array lookup
(!) :: Ix i => Array i e -> i -> e
-- Array update
(//) :: Ix i => Array i e -> [(i, e)] -> Array i e
myArray = listArray (7,11) ["seven","eight","nine","ten","eleven"]
ghci> myArray
array (7,11) [(7,"seven"),(8,"eight"),(9,"nine"),(10,"ten"),(11,"eleven")]
ghci> :t myArray
myArray :: Array Integer String
ghci> myArray ! 8
"eight"
ghci> myArray // [(8,"ocho"),(9,"nueve")]
array (7,11) [(7,"seven"),(8,"ocho"),(9,"nueve"),(10,"ten"),(11,"eleven")]
ghci> myArray
array (7,11) [(7,"seven"),(8,"eight"),(9,"nine"),(10,"ten"),(11,"eleven")]
Folding over Maps & Arrays
length (array (7,11) [(7,"seven"),(8,"eight"),(9,"nine"),(10,"ten"),(11,"ELEVEN")])
==> 5
foldr (+) 0 (Map.fromList [("banana",3),("egg",7)])
==> 10
stack haddock --open
stack haddock --open <package>