람다 Lambda
단 한번만 함수가 필요할 때 사용하는 익명의 함수. 람다는 표현식이다.
선언방법
- \ 로 선언
- 그 뒤에는 함수의 매개변수를 쓴다. (하나 이상의 매개변수는 공백으로 구분한다)
- -> 뒤에는 함수의 내용이 온다.
- 괄호로 람다를 감싸는 것이 일반적이다.
where 절로 바인딩해서 표현했던 방식 대신 람다로 변경한 numLongChains 예시
numLongChains :: Int
numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))
람다보다는..?
부분적 어플리케이션이 람다보다 가독성이 좋은 경우도 있다.
map (+3) [1,6,3,2] // 부분적인 어플리케이션
map (\x -> x + 3) [1,6,3,2] // 람다 사용
여러개의 매개변수를 받을 수 있다
ghci> zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]
[153.0,61.5,31.0,15.75,6.6]
패턴 매칭
일반함수와 동일하게 패턴매칭을 할 수 있다.
다만 차이점은, 하나의 매개변수에 대해 여러개의 패턴을 정의할 수 없다.
ghci> map (\(a,b) -> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)]
[3,8,9,8,7]
폴드
리스트와 같은 데이터 구조를 단일 값으로 변경시키는 함수.
map 과 비슷하지만, 다른 리스트로 변경하는 map과는 다르게 fold는 하나의 값으로 축소시킨다.
구성?
foldl [binary function] [accumulator] [list]
(foldl) :: Foldable t => (b -> a -> b) -> b -> t a -> b
- 리스트는 왼쪽 or 오른쪽에서 폴드될 수 있다
- 누적기(accumulator) 와 매개변수인 리스트의 첫 번째 (or 마지막) 요소를 이용해 주어진 바이너리 함수를 호출한다.
- 위의 요소로 바이너리 함수를 호출한 결과값이 새로운 누적기 값이 된다.
- 새로운 누적기 값과 리스트의 요소로 또 다른 누적기를 만든다.
- 하나의 누적값으로 남을때까지 반복한다.
foldl (레프트 폴드)
- 왼쪽에서부터 리스트를 폴드한다.
- 바이너리 함수 = 누적기, 리스트의 헤드에 적용 된다.
sum 함수를 foldl로 재구현
sum' :: (Num a) => [a] -> a
sum' xs = foldl (\acc x -> acc + x) 0 xs
// testing
ghci> sum' [3,5,2,1]
11
다음은 foldl이 순차적으로 어떤 방식으로 실행되는지에 대한 그림이다.
왼쪽에 위치하는 값들은 누적기의 값이고, 최종적으로는 foldl의 결과값이 된다.
누적기가 왼쪽에 위치하고 있어서 레프트 폴드이다. (라이트 폴드의 경우에는 오른쪽에 누적기가 위치하게 된다)
- 시작값(=누적기) = 0, 리스트 헤드값 = 3 을 \acc x -> acc + x 람다에서 계산을 한다 => 3
- 누적기 = 3, 리스트 헤드값 = 5, 람다계산 결과 = 8
- 누적기 = 8, 리스트 헤드값 = 2, 람다계산 결과 = 10
- 누적기 = 10, 리스트 헤드값 = 1, 람다계산 결과 = 11
- 누적기 = 11, 리스트에 다른 값이 존재하지 않으므로 연산 종료.
커리된 함수로 표현
sum' :: (Num a) => [a] -> a
sum' = foldl (+) 0 // 리스트를 받는 함수를 반환
foldr (라이트 폴드)
- 오른쪽에서부터 리스트를 폴드한다.
- 레프트 폴드와 반대로 현재값이 먼저 오고 그 다음에 누적기가 온다.
- 누적기 값은 어떤 타입도 될 수 있다.
\acc x -> ... // foldl
\x acc -> ... // foldr
라이트 폴드로 map을 구현
map' :: (a -> b) -> [a] -> [b]
map' f xs = foldr (\x acc -> f x : acc) [] xs
실행결과
ghci>map' (+3) [1,2,3]
[4,5,6]
동일한 동작을 하지만 foldl로 구현
map' :: (a -> b) -> [a] -> [b]
map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs
++ 함수가 : 보다 느리기 때문에 리스트에서 새로운 리스트를 만들어낼때는 foldr를 사용하는것을 권장.
foldl1 과 foldr1
- foldl, foldr과 동일하게 동작하지만 명시적으로 시작 값을 누적기에 제공하지 않아도 된다.
- foldl1 의 누적기 = 리스트의 첫번째 값
- foldr1 의 누적기 = 리스트의 마지막 값
- 적어도 하나 이상의 요소가 필요. 빈 리스트로 호출 시 런타임 에러 발생.
maximum' :: (Ord a) => [a] -> a
maximum' = foldl max
폴드 예제
maximum' :: (Ord a) => [a] -> a
maximum' = foldr1 (\x acc -> if x > acc then x else acc)
reverse' :: [a] -> [a]
reverse' = foldl (\acc x -> x : acc) []
product' :: (Num a) => [a] -> a
product' = foldr1 (*)
filter' :: (a -> Bool) -> [a] -> [a]
filter' p = foldr (\x acc -> if p x then x : acc else acc) []
head' :: [a] -> a
head' = foldr1 (\x _ -> x)
last' :: [a] -> a
last' = foldl1 (\_ x -> x)
스캔
- scanl, scanr 함수는 리스트 형태로 중간 누적기를 표현한다.
- foldl, foldr과 매우 유사한 함수.
- scanl의 결과값은 리스트의 마지막 값.
- scanr의 결과값은 리스트의 head 값.
- 보통 모니터링 하는 용도로 사용한다.
- 누적기의 값을 순차적으로 배열에 표현한다.
스캔의 타입
ghci>:t scanl
scanl :: (b -> a -> b) -> b -> [a] -> [b]
예제
ghci> scanl (+) 0 [3,5,2,1]
[0,3,8,10,11]
ghci> scanr (+) 0 [3,5,2,1]
[11,8,3,1,0]
ghci> scanl1 (\acc x -> if x > acc then x else acc) [3,4,5,3,7,9,2,1]
[3,4,5,5,7,9,9,9]
ghci> scanl (flip (:)) [] [3,2,1]
[[],[3],[2,3],[1,2,3]]
$를 가진 함수 애플리케이션
- 함수 애플리케이션 연산자 = $ 함수
- 가장 낮은 우선순위를 가짐
- $ (오른쪽 연관) vs 공백 (왼쪽 연관)
- 너무나도 많은 괄호를 사용하지 않도록 하게 해주는 편의 함수
- 함수 애플리케이션을 또 다른 함수처럼 취급할 수 있게 한다
괄호 대신 $를 사용하는 예제
sum (map sqrt [1..130])
sum $ map sqrt [1..130] // $를 사용
또 다른 함수처럼 취급이 가능하게 하는 예제
ghci> map ($ 3) [(4+), (10*), (^2), sqrt]
[7.0,30.0,9.0,1.7320508075688772]
합성함수
- 수학에서의 합성함수의 의미와 동일.
- . 기호를 사용하여 합성함수를 표현한다.
- 합성함수는 다른함수에 전달하기 위한 함수를 즉시 만들기 위해서 사용한다.
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
람다를 대체할 수 있는 경우를 종종 볼 수 있다. 보통 합성함수가 더 간결하여서 선호하는 경우도 있는데, 이는 (개인적인 생각으로는) 개인의 취향인 것 같다.
// 람다표현
ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]
[-5,-3,-6,-7,-3,-2,-19,-24]
// 합성함수 표현
ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24]
[-5,-3,-6,-7,-3,-2,-19,-24]
합성함수는 오른쪽 연관 (right associative = operation들을 오른쪽에서 부터 묶는 방식) 이기 때문에 여러 함수를 합성할 수 있다.
// 람다 표현
ghci> map (\xs -> negate (sum (tail xs))) [[1..5],[3..6],[1..7]]
[-14,-15,-27]
// 합성 함수
ghci> map (negate . sum . tail) [[1..5],[3..6],[1..7]]
[-14,-15,-27]
여러개의 매개변수를 갖는 합성 함수
여러개의 매개변수를 받는 함수들을 합성함수로 사용하려면, 각각의 함수가 하나의 매개변수를 받도록 부분적으로 적용해야한다.
sum (replicate 5 (max 6.7 8.9))
하나의 매개변수를 받도록 적용
(sum . replicate 5) max 6.7 8.9
$사용
sum . replicate 5 $ max 6.7 8.9
포인트 프리 스타일
합성함수의 매개변수를 명시적으로 표현하지 않는 방식.
sum' :: (Num a) => [a] -> a
sum' xs = foldl (+) 0 xs
sum의 매개변수 xs와 오른쪽에 선언된 xs를 생략하는 방식이 포인트 프리 스타일이다.
sum' = foldl (+) 0
- 대부분 가독성 높고 간결
- 함수가 복잡한 경우 포인트 프리 스타일로 작성하는 것을 권장하지 않음
- 복잡한 경우, let 바인딩을 사용 or 작은 문제로 분리
복잡한 함수 예시
oddSquareSum :: Integer
oddSquareSum = sum (takeWhile (<10000) (filter odd (map (^2) [1..])))
합성함수 적용
oddSquareSum :: Integer
oddSquareSum = sum . takeWhile (<10000) . filter odd . map (^2) $ [1..] // 원문
oddSquareSum = sum . takeWhile (<10000) . filter odd . map $ (^2) [1..] // 책
let을 사용한 예시
oddSquareSum :: Integer
oddSquareSum =
let oddSquares = filter odd $ map (^2) [1..]
belowLimit = takeWhile (<10000) oddSquares
in sum belowLimit
'devlog > programming language' 카테고리의 다른 글
[Hashkell] 하스켈이란? 하스켈 설치방법, 실행방법 (0) | 2020.01.18 |
---|