Haskell에서 <$= (fmap . const) 작동 방식
fmap 함수 이해:
fmap
함수는 Functor
인스턴스의 값을 함수에 적용하여 새로운 값을 반환하는 함수입니다. 즉, fmap
은 기존 값을 함수로 변환하고, 변환된 함수를 다시 값에 적용하여 결과를 도출합니다. 간단히 말해, fmap
은 함수를 "맵핑"하는 역할을 합니다.
const 함수 이해:
const
함수는 단 하나의 인수를 받아 그 인수를 그대로 반환하는 함수입니다. 즉, const
함수는 입력값에 어떠한 변화도 일으키지 않고 그대로 유지합니다.
<$= 연산자 작동 방식:
<$=
연산자는 fmap
함수와 const
함수를 조합하여 다음과 같은 작동 방식을 가집니다.
- 우측에 있는 값을
const
함수에 적용합니다. const
함수로부터 반환된 함수를 왼쪽에 있는Functor
인스턴스의 값에fmap
함수를 통해 적용합니다.- 최종적으로
fmap
함수의 결과 값을 반환합니다.
예시:
data Maybe a = Nothing | Just a
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
addOne :: Int -> Int
addOne x = x + 1
maybeAddOne :: Maybe Int -> Maybe Int
maybeAddOne = $ const addOne
위 예시에서 maybeAddOne
함수는 Maybe Int
인스턴스를 받아 addOne
함수를 적용합니다. 하지만 addOne
함수는 Int
값만 받아들이기 때문에 Maybe Int
값을 직접 적용할 수 없습니다.
따라서 maybeAddOne
함수는 const addOne
을 사용하여 addOne
함수를 상수값으로 변환하고, fmap
함수를 통해 Maybe Int
값에 적용합니다.
만약 Maybe Int
값이 Just x
형태라면 fmap
함수는 addOne x
를 계산하여 Just (x + 1)
값을 반환합니다. 반면에 Maybe Int
값이 Nothing
형태라면 fmap
함수는 Nothing
을 그대로 반환합니다.
예제 코드: fmap
, const
, $<=
활용
문자열 리스트 대문자 변환:
data List a = Nil | Cons a (List a)
instance Functor List where
fmap f Nil = Nil
fmap f (Cons x xs) = Cons (f x) (fmap f xs)
upper :: Char -> Char
upper c = Char.toUpper c
upperCaseList :: List String -> List String
upperCaseList = $ const upper
이 코드는 List
데이터 타입의 인스턴스에 대한 Functor
클래스 구현과 upperCaseList
함수를 보여줍니다.
upperCaseList
함수는List String
값을 받아 각 문자열을upper
함수로 변환하여 새로운List String
값을 반환합니다.$<=
연산자를 사용하여upper
함수를 상수값으로 변환하고fmap
함수에 적용합니다.
숫자 리스트 제곱:
square :: Int -> Int
square x = x * x
squaredList :: [Int] -> [Int]
squaredList = $ const square
squaredList
함수는[Int]
값을 받아 각 숫자를square
함수로 제곱하여 새로운[Int]
값을 반환합니다.
Maybe 값 증가:
addOne :: Maybe Int -> Maybe Int
addOne = $ const (+1)
이 코드는 Maybe Int
값에 1을 더하는 addOne
함수를 보여줍니다.
Maybe Int
값이Just x
형태라면fmap
함수는x + 1
을 계산하여Just (x + 1)
값을 반환합니다.Maybe Int
값이Nothing
형태라면fmap
함수는Nothing
을 그대로 반환합니다.
위 예제 코드들은 fmap
, const
, $<=
연산자를 활용하여 다양한 데이터 변환 작업을 간결하고 효율적으로 수행하는 방법을 보여줍니다.
fmap . const
대신 사용 가능한 방법들
직접적인 fmap 함수 사용:
가장 기본적인 방법은 fmap
함수를 직접 사용하는 것입니다. 이는 명확하고 간결한 코드를 작성하는 데 도움이 될 수 있습니다.
data Maybe a = Nothing | Just a
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
addOne :: Int -> Int
addOne x = x + 1
maybeAddOne :: Maybe Int -> Maybe Int
maybeAddOne x = fmap addOne x
위 예시에서 maybeAddOne
함수는 fmap
함수를 직접 사용하여 Maybe Int
값에 addOne
함수를 적용합니다.
익명 함수 사용:
lambda
표현식을 사용하여 익명 함수를 정의하고 fmap
함수와 함께 사용할 수 있습니다. 이는 간결한 코드를 작성하는 데 도움이 될 수 있으며, 특히 함수가 간단한 경우 유용합니다.
maybeAddOne :: Maybe Int -> Maybe Int
maybeAddOne = fmap (\x -> x + 1)
위 예시에서 maybeAddOne
함수는 익명 함수 (\x -> x + 1)
을 사용하여 Maybe Int
값에 1을 더합니다.
liftM 함수 사용:
Control.Monad
모듈에서 제공하는 liftM
함수를 사용할 수 있습니다. 이 함수는 Functor
인스턴스 간 데이터 변환을 위한 더욱 추상적인 방법을 제공하며, 좀 더 복잡한 상황에서 유용할 수 있습니다.
import Control.Monad
maybeAddOne :: Maybe Int -> Maybe Int
maybeAddOne = liftM (+1)
다른 Functor 클래스 인스턴스 활용:
특정 상황에서는 다른 Functor
클래스 인스턴스를 활용하여 더욱 간결하고 효율적인 코드를 작성할 수 있습니다. 예를 들어, Either
타입을 사용하여 오류 처리를 수행할 경우 Either
인스턴스에 대한 Functor
클래스 구현을 활용하는 것이 유용할 수 있습니다.
haskell functor higher-order-functions