【发布时间】:2015-02-12 15:58:21
【问题描述】:
出于教育原因,我尝试在 C# 中实现 F# 中的选择和选项类型。这是受《真实世界函数式编程》一书和一些博客文章的启发,例如:http://bugsquash.blogspot.de/2011/08/refactoring-to-monadic-c-applicative.html 和 http://tomasp.net/blog/idioms-in-linq.aspx/。
我想让它工作,但我不知道如何实现选择类型的扩展(Bind、Map、SelectMany、...):
public static void Division()
{
Console.WriteLine("Enter two (floating point) numbers:");
(
from f1 in ReadDouble().ToChoice("Could not parse input to a double.")
from f2 in ReadDouble().ToChoice("Could not parse input to a double.")
from result in Divide(f1, f2).ToChoice("Cannot divide by zero.")
select result
)
.Match(
x => Console.WriteLine("Result = {0}", x),
x => Console.WriteLine("Error: {0}", x));
}
public static Option<double> Divide(double a, double b)
{
return b == 0 ? Option.None<double>() : Option.Some(a / b);
}
public static Option<Double> ReadDouble()
{
double i;
if (Double.TryParse(Console.ReadLine(), out i))
return Option.Some(i);
else
return Option.None<double>();
}
public static Option<int> ReadInt()
{
int i;
if (Int32.TryParse(Console.ReadLine(), out i))
return Option.Some(i);
else
return Option.None<int>();
}
}
选项类型如下所示:
public enum OptionType
{
Some, None
}
public abstract class Option<T>
{
private readonly OptionType _tag;
protected Option(OptionType tag)
{
_tag = tag;
}
public OptionType Tag { get { return _tag; } }
internal bool MatchNone()
{
return Tag == OptionType.None;
}
internal bool MatchSome(out T value)
{
value = Tag == OptionType.Some ? ((Some<T>)this).Value : default(T);
return Tag == OptionType.Some;
}
public void Match(Action<T> onSome, Action onNone)
{
if (Tag == OptionType.Some)
onSome(((Some<T>)this).Value);
else
onNone();
}
public Choice<T, T2> ToChoice<T2>(T2 value)
{
if (Tag == OptionType.Some)
{
T some;
MatchSome(out some);
return Choice.NewChoice1Of2<T, T2>(some);
}
else
return Choice.NewChoice2Of2<T, T2>(value);
}
}
internal class None<T> : Option<T>
{
public None() : base(OptionType.None) { }
}
internal class Some<T> : Option<T>
{
public Some(T value)
: base(OptionType.Some)
{
_value = value;
}
private readonly T _value;
public T Value { get { return _value; } }
}
public static class Option
{
public static Option<T> None<T>()
{
return new None<T>();
}
public static Option<T> Some<T>(T value)
{
return new Some<T>(value);
}
}
public static class OptionExtensions
{
public static Option<TResult> Map<T, TResult>(this Option<T> source, Func<T, TResult> selector)
{
T value;
return source.MatchSome(out value) ? Option.Some(selector(value)) : Option.None<TResult>();
}
public static Option<TResult> Bind<T, TResult>(this Option<T> source, Func<T, Option<TResult>> selector)
{
T value;
return source.MatchSome(out value) ? selector(value) : Option.None<TResult>();
}
public static Option<TResult> Select<T, TResult>(this Option<T> source, Func<T, TResult> selector)
{
return source.Map(selector);
}
public static Option<TResult> SelectMany<TSource, TValue, TResult>(this Option<TSource> source, Func<TSource, Option<TValue>> valueSelector, Func<TSource, TValue, TResult> resultSelector)
{
return source.Bind(s => valueSelector(s).Map(v => resultSelector(s, v)));
}
}
这里是选择类型的实现:
public enum ChoiceType { Choice1Of2, Choice2Of2 };
public abstract class Choice<T1, T2>
{
private readonly ChoiceType _tag;
protected Choice(ChoiceType tag)
{
_tag = tag;
}
public ChoiceType Tag { get { return _tag; } }
internal bool MatchChoice1Of2(out T1 value)
{
value = Tag == ChoiceType.Choice1Of2 ? ((Choice1Of2<T1, T2>)this).Value : default(T1);
return Tag == ChoiceType.Choice1Of2;
}
internal bool MatchChoice2Of2(out T2 value)
{
value = Tag == ChoiceType.Choice2Of2 ? ((Choice2Of2<T1, T2>)this).Value : default(T2);
return Tag == ChoiceType.Choice2Of2;
}
public void Match(Action<T1> onChoice1Of2, Action<T2> onChoice2Of2)
{
if (Tag == ChoiceType.Choice1Of2)
onChoice1Of2(((Choice1Of2<T1, T2>)this).Value);
else
onChoice2Of2(((Choice2Of2<T1, T2>)this).Value);
}
}
internal class Choice1Of2<T1, T2> : Choice<T1, T2>
{
public Choice1Of2(T1 value)
: base(ChoiceType.Choice1Of2)
{
_value = value;
}
private readonly T1 _value;
public T1 Value { get { return _value; } }
}
internal class Choice2Of2<T1, T2> : Choice<T1, T2>
{
public Choice2Of2(T2 value)
: base(ChoiceType.Choice2Of2)
{
_value = value;
}
private readonly T2 _value;
public T2 Value { get { return _value; } }
}
public static class Choice
{
public static Choice<T1, T2> NewChoice1Of2<T1, T2>(T1 value)
{
return new Choice1Of2<T1, T2>(value);
}
public static Choice<T1, T2> NewChoice2Of2<T1, T2>(T2 value)
{
return new Choice2Of2<T1, T2>(value);
}
}
编辑:
它实际上适用于下面的扩展。我真正不喜欢的是这个实现向 Choice 类型添加了特定于上下文的行为。这是因为 Choice1Of2 是首选,因为所有扩展方法都主要在它上操作,而不是在 Choice2Of2 上或两者兼而有之。 (但这就是消费代码的实际含义,所以我想这是让它工作的唯一方法。)
public static Choice<TResult, T2> Map<T1, T2, TResult>(this Choice<T1, T2> source, Func<T1, TResult> selector)
{
T1 value1;
if(source.MatchChoice1Of2(out value1))
{
return Choice.NewChoice1Of2<TResult, T2>(selector(value1));
}
T2 value2;
if (source.MatchChoice2Of2(out value2))
{
return Choice.NewChoice2Of2<TResult, T2>(value2);
}
throw new InvalidOperationException("source (:Choice) has no value.");
}
public static Choice<TResult, T2> Bind<T1, T2, TResult>(this Choice<T1, T2> source, Func<T1, Choice<TResult, T2>> selector)
{
T1 value1;
if (source.MatchChoice1Of2(out value1))
{
return selector(value1);
}
T2 value2;
if (source.MatchChoice2Of2(out value2))
{
return Choice.NewChoice2Of2<TResult, T2>(value2);
}
throw new InvalidOperationException("source (:Choice) has no value.");
}
public static Choice<TResult, T2> Select<T1, T2, TResult>(this Choice<T1, T2> source, Func<T1, TResult> selector)
{
return source.Map(selector);
}
public static Choice<TResult, T2> SelectMany<TSource, TValue, T2, TResult>(this Choice<TSource, T2> source, Func<TSource, Choice<TValue, T2>> valueSelector, Func<TSource, TValue, TResult> resultSelector)
{
return source.Bind(s => valueSelector(s).Map(v => resultSelector(s, v)));
}
【问题讨论】:
-
你有什么问题?
-
为了使第一个示例中的代码能够正常工作,编译器正在寻找选择类型中 Select 和 SelectMany 的实现。我不知道如何实现它们。在选项类型中它工作得很好,但在选择中你不需要两个选择器函数来选择 T1 和 T2 类型。这将如何运作?
-
嗨@mauricio,谢谢。但是我错过了我特别感兴趣的 Select 和 SelectMany 的实现。
-
@user1993065 它们作为扩展方法编写起来很简单。
标签: c# linq f# functional-programming monads