Contravariant Functor, Profunctor
Contravariant Functor
화살표의 방향이 반대가 되는 Functor
입력과 출력이 반대가 되는 Functor이다.
반공변 Functor 라고 할 수 있다.
Hashkell 에서는 다음과 같이 정의 한다.
contramap :: Contravariant f => (b -> a) -> f a -> f b
C# 은 아래와 같은 형태 이다.
public Contravariant<T1> ContraMap<T1>(Func<T1, T> selector)
C# 에서 covariant functor (일반 Functor)
// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;
IEnumerable<T> 를 통해 대표적인 Covariant functor 를 표현했다.
// Contravariance.
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;
Action<T> 를 통해 대표적인 Contravariant functor 를 표현했다.
즉 Contravariant functor 는 Adapter 의 역할을 한다고 생각하면 쉽다.
즉 입력으로 int 값을 받아야 하는데 int 가 아닌 string 을 받는 function 이 있다면
일단 string 을 int 로 변형하는 Conversion(Selector) 를 구현하고
그 Selector 를 먼저 실행시켜서 string 을 int 로 변형하고
실제 적용하려는 Functor 를 그 이후에 실행 하면 된다.
이런식으로 사용하기 위해 Contravariant functor 가 존재 한다.
아래는 이를 간단히 예로든 코드 이다.
public interface IReader<R, A>
{
A Run(R environment);
}
public static partial class IReaderExtension
{
public static IReader<R1, A> ContraMap<R, R1, A>(this IReader<R, A> reader,
Func<R1, R> selector)
{
return new FuncReader<R1, A>(r => reader.Run(selector(r)));
}
private sealed class FuncReader<R, A> : IReader<R, A>
{
private readonly Func<R, A> func;
public FuncReader(Func<R, A> func)
{
this.func = func;
}
public A Run(R environment)
{
return func(environment);
}
}
}
public sealed class MinutesReader : IReader<int, TimeSpan>
{
public TimeSpan Run(int environment)
{
return TimeSpan.FromMinutes(environment);
}
}
public sealed class BooleanReader : IReader<bool, int>
{
public int Run(bool environment)
{
return environment ? 1:2;
}
}
// projected, tProjected 는 string -> int 로
// string --> bool 로 변경하는 역할을 하는 Conversion 함수를
// ContraMap 에 적용시킨다.
// 이럴경우 ContraMap 은 adapter 역할을 하게 된다.
IReader<int, TimeSpan> r = new MinutesReader();
IReader<string, TimeSpan> projected = r.ContraMap((string s) => int.Parse(s));
WriteLine(new TimeSpan(0, 21, 0) == projected.Run("21"));
IReader<bool, int> t = new BooleanReader();
IReader<string,int> tProjected = t.ContraMap((string s) => bool.Parse(s));
WriteLine(2 == tProjected.Run("False"));
문자열 "21" 을 ContraMap 에 정수형 으로 변경하는 (int.Parse) 함수를 적용하고
해당 Functor(Contravariant) 를 이용하여 run 하고 있다.
(adaptor 역할을 하고 있다. )
즉 Selector 가 먼저 Reader 가 나중에 실행된다.
원래 Map 같은 경우는 아래와 같다.
public static IReader<R, B> Map<A, B, R>(this IReader<R, A> reader,
Func<A, B> selector)
{
return new FuncReader<R, B>(r => selector(reader.Run(r)));
}
Reader 가 먼저 Selector 가 나중에 실행된다.
Reader --> Selector 가 ContraMap 에서는 Selector --> Reader 로 반전 되어 있는 형태이다.
ProFunctor
BiFunctor 에서 argument 중 하나에 Contravariant 을 적용시킨 Functor 이다.
일반 적으로 첫번재 argument 에 대해 Contravariant 를
두번째 변수에는 Covariant 를 적용시킨다.
Profunctor<A,B> 에서 A 는 Contravariant, B 는 Covariant 이다.
그리고 이러한 mapping 을 지원하기 위한 함수 를 DiMap 이라고 한다.
위 IReader 에 대한 DiMap 을 구현해 보자
public static partial class IReaderExtension
{
public static IReader<R1, A1> DiMap<R, R1, A, A1>(
this IReader<R, A> reader,
Func<R1, R> contraSelector,
Func<A, A1> coSelector)
{
return reader.ContraMap(contraSelector).Map(coSelector);
}
...
...
}
IReader 를 이용하여 TotalSecondsReader 를 구현해보자
public sealed class TotalSecondsReader : IReader<DateTimeOffset, double>
{
public double Run(DateTimeOffset environment)
{
WriteLine(environment.ToUnixTimeSeconds());
return environment.ToUnixTimeSeconds();
}
}
마지막으로 사용해 보자
IReader<DateTimeOffset, double> reader = new TotalSecondsReader();
IReader<DateTime, bool> projection =
reader.DiMap((DateTime dt) => DateTime.SpecifyKind(dt, DateTimeKind.Local), d => d % 2 == 0);
WriteLine(projection.Run(DateTime.Now));
현재시간을 받아서 그것을 DateTimeOffset 형태로 바꾸고 해당 하는 값을 totalseconds 로 변경한다.
그게 첫번째 argument 인 Contravariant 가 하는 일이고 이것은 ContraMap 에 의해 처리 된다..
두번째 argument 는 첫번째 argument 의 결과 값을 2로 나누어 남은 나머지가 0 인지 비교한다.
즉 event 값인지 체크하고 true 또는 false 를 return 하게 된다.
관련영상