ApplicativeFunctor, BiFunctor

2022. 11. 7. 00:00Functional Programming/Category Theory

반응형

참고 : https://blog.ploeh.dk/2018/03/19/functors-applicatives-and-friends/

 

Applicative Functor

일반적인 Functor (fmap) 가 하나의 argument 를 가지고 사용 되는 Functor 라면

Applicative Functor 은 두개의 argument 를 가지고 fmap 하려는 경우 사용 하는 Functor 이다.

이항 연산을 예로 들면

 

두개의 인자를 받아야 하는데 fmap 같은 경우는  하나의 인자만 받을수 있으니 이것이 힘들다

그래서 이 함수 자체를 감싼 Functor 를 만들어 사용하면 해결 된다. 

add(left)(right) = left + right
map(add)([1,2,3])
-- [ add(1),
--   add(2),
--   add(3) ]

위와 같이 하면 left 에는 1 이 들어가지만 right 에는 어떤 값도 넣을수 없으니 불가능 하다
이럴 경우 add1 이라는 partial 함수를 만들어 적용 할 수 있다.
또한 다른 방법들도 있는데 Haskell 에서는 아래와 같다. 

pure(input) = [input]
apply(functions)(list) =
  [ element | function <- functions,
              element  <- map(function)(list)
  ]
apply(pure(add1))([1,2,3])
-- [2,3,4]
apply(apply(pure(add))([1,2,3]))([4,5,6])
-- [5,6,7,6,7,8,7,8,9]
apply(map(add)([1,2,3]))([4,5,6])        
-- [5,6,7,6,7,8,7,8,9]

 

 

함수를 감싼 Functor

List 안에 함수가 들어 있다고 생각하면 쉽다.

결과적으로 이 functor 의 의해 나오는 값(??) 은

함수들을 적용하여 나올수 있는 모든 경우의 수가 나온다.

f1 , f2, f3 를   특정 List 에 적용하면

f1 을 적용한 값 , f2 를 적용한 값 , f3 를 적용한 값을 볼수 있다.

 

카드 게임에서 나올수 있는 목록의 종류

정해진 문자열을 이용하여 만들수 있는 password 의 종류

 

등등 이런 분야에 응용하여 사용할 수 있다. 

 

추상화 하면 아래와 같은 형태일 것이다. 

public static class ApplicativeFunctors
{
    public static IEnumerable<TResult> Apply<T, TResult>(
    this IEnumerable<Func<T, TResult>> selectors,
    IEnumerable<T> source)
    {
        return selectors.SelectMany(source.Select);
    }

}

요런식으로 사용할 수 있다. 

var func1 = (int x) => x + 1;
var func2 = (int x) => x + 2;
var func3 = (int x) => x + 3;

var sourceList = new List<int> { 1, 2, 3, 4, 5 };

var applicativeFunctor = new List<Func<int, int>>();
applicativeFunctor.Add(func1);
applicativeFunctor.Add(func2);
applicativeFunctor.Add(func3);


var datas = applicativeFunctor.Apply(sourceList);

foreach (var item in datas)
    WriteLine(item);

 

Bi Functor

일반적으로 하나의 Generic Type 을 mapping 하는 function(??) 을 Functor 라고 한다

Normal Functor

두개의 Generic Type 을 mapping 하는 Functor 를 BiFunctor 라고 할 수 있다. 

Bi Functor

 Bar<Left, Right> 라고 정의 했을때 Left 를 먼저 mapping 하고 Right 를 mapping 하는 경우가 있고

Right 를 먼저 mapping 하고 Left 를 mapping 하는 경우도 있다.

Anyway ... bimap 은 두 값을 한번에 mapping 한다. 

위의 예에서는 Bar<string, int> 를 Bar<bool, DateTime> 으로 변환한 것이다. 

 

Bifunctor 의 예는 Either<Left,Right>Tuple<T,U> 이 있다.

 

Tuple 을 이용하여 Bi Functor 를 구현해 보자

public static class TupleExtensions
{
    public static Tuple<T, U2> Select<T, U1, U2>(
    this Tuple<T, U1> source,
    Func<U1, U2> selector)
    {
        return SelectSecond(source, selector);
    }
    public static Tuple<T, U2> SelectSecond<T, U1, U2>(
    this Tuple<T, U1> source,
    Func<U1, U2> selector)
    {
        return Tuple.Create(source.Item1, selector(source.Item2));
    }

    public static Tuple<T2, U> SelectFirst<T1, T2, U>(
    this Tuple<T1, U> source,
    Func<T1, T2> selector)
    {
        return Tuple.Create(selector(source.Item1), source.Item2);
    }

    public static Tuple<T2, U2> SelectBoth<T1, T2, U1, U2>(
    this Tuple<T1, U1> source,
    Func<T1, T2> selector1,
    Func<U1, U2> selector2)
    {
        return source.SelectFirst(selector1).SelectSecond(selector2);
    }
}
var t = Tuple.Create("foo", 42);
// get first character
WriteLine(t.SelectFirst(s => s.First()));
// even check
var actual = from i in t
             select i % 2 == 0;
//t.SelectSecond(i=>i % 2 == 0);
WriteLine(actual.ToString());

WriteLine(t.SelectBoth(s => s.First(), i => i % 2 == 0));

 

 

관련영상

https://youtu.be/TRH0Tn4C-So

 

 

반응형

'Functional Programming > Category Theory' 카테고리의 다른 글

Monad  (0) 2022.11.21
Contravariant Functor, Profunctor  (0) 2022.11.14
Functor  (0) 2022.10.31
Product and CoProduct  (0) 2022.10.24
Kleisli Catogories  (0) 2022.10.17