今天我看到了一段代码,我正在考虑它的意义。所以我做了一个小例子,你可以看到它是如何工作的。 据我所知,它应该是大量使用Lambda-Caculus的嵌套功能,就像它在功能语言中使用一样。 因为我发现这是不可理解的(从文件跳转到文件)我只想知道你们中的某个人是否有类似这样的经历。
示例功能一
public void DoSomethingA(int inputInt, Action<int?, Exception> result)
{
try
{
int? retVal = inputInt;
result(retVal, null);
}
catch (Exception e)
{
result(null, e);
}
}
示例功能二
static void DoSomethingB(int? baseInt, Action<Exception> result)
{
try
{
result(null);
}
catch (Exception e)
{
result(e);
}
}
示例调用两者。
public int? CalculateSomething(int input)
{
int? result;
DoSomethingA(input, (resultInt, aException) =>
{
if (aException != null)
{
return;
}
DoSomethingB(resultInt, bException =>
{
if (bException != null)
{
return;
}
result = resultInt;
});
});
return null;
}
可能有趣的是,“主要”功能将具有某种承诺。所以它注册了函数并等待结束然后获得结果。
答案 0 :(得分:1)
你所描述的是Monad。特别是Try
monad。我将在下面展示如何实现Try monad,CalculateSomething
的代码如下所示:
public Try<int> CalculateSomething(int input) =>
from x in DoSomethingA(input)
from y in DoSomethingB(x)
select y;
任何措施都很容易理解。
首先声明一个代表Try操作的委托:
public delegate TryResult<T> Try<T>();
接下来定义TryResult<T>
。它将捕获返回值on-success,以及失败异常on-fail:
public struct TryResult<T>
{
internal readonly T Value;
internal readonly Exception Exception;
public TryResult(T value)
{
Value = value;
Exception = null;
}
public TryResult(Exception e)
{
Exception = e;
Value = default(T);
}
public static implicit operator TryResult<T>(T value) =>
new TryResult<T>(value);
internal bool IsFaulted => Exception != null;
public override string ToString() =>
IsFaulted
? Exception.ToString()
: Value.ToString();
public static readonly TryResult<T> Bottom = new InvalidOperationException();
}
接下来为Try
委托定义一些扩展方法(这是委托支持扩展方法的C#的一个鲜为人知的特性):
public static class TryExtensions
{
public static TryResult<T> Try<T>(this Try<T> self)
{
try
{
if (self == null) return TryResult<T>.Bottom;
return self();
}
catch (Exception e)
{
return new TryResult<T>(e);
}
}
public static R Match<T, R>(this Try<T> self, Func<T, R> Succ, Func<Exception, R> Fail)
{
var res = self.Try();
return res.IsFaulted
? Fail(res.Exception)
: Succ(res.Value);
}
}
他们都允许以安全的方式调用Try
委托。关键是Match
扩展方法,其中&#39;模式匹配&#39;结果:
int res = CalculateSomething(1).Match(
Succ: value => value,
Fail: excep => 0
);
所以你被迫承认该函数可以抛出一个异常来获取该值。
这里缺少的一件事是它如何与LINQ一起使用:
public static class TryExtensions
{
public static Try<U> Select<T, U>(this Try<T> self, Func<T, U> select) =>
new Try<U>(() =>
{
var resT = self.Try();
if (resT.IsFaulted) return new TryResult<U>(resT.Exception);
return select(resT.Value);
});
public static Try<V> SelectMany<T, U, V>(
this Try<T> self,
Func<T, Try<U>> bind,
Func<T, U, V> project ) =>
new Try<V>(() =>
{
var resT = self.Try();
if (resT.IsFaulted) return new TryResult<V>(resT.Exception);
var resU = bind(resT.Value).Try();
if (resU.IsFaulted) return new TryResult<V>(resT.Exception);
return new TryResult<V>(project(resT.Value, resU.Value));
});
}
Select
允许此功能:
var res = from x in y
select x;
SelectMany
允许此功能:
var res = from x in y
from z in x
select z;
这就是它允许多个from语句按顺序运行。这被称为monadic bind(但你不需要知道为了这个工作 - 我不想在这里写一个monad教程)。基本上它是在CalculateSomething
示例中捕获嵌套模式,因此您无需再次手动编写它。
就是这样。上面的代码,你只写一次和一次。现在让我们实现您的DoSomething
功能:
public Try<int> DoSomethingA(int inputInt) => () =>
inputInt;
public Try<int> DoSomethingB(int inputInt) => () =>
{
throw new Exception();
};
注意他们如何被定义为lambdas。如果您使用C#5或更低版本,它们将如下所示:
public Try<int> DoSomethingA(int inputInt)
{
return () => inputInt;
}
如果您希望更全面地了解此问题,请查看我的language-ext functional library for C#那里有Try implementation的地方。它有许多有用的扩展方法,允许您编写功能代码,而不需要使用try / catch的bolierplate。
答案 1 :(得分:0)
有时,遵循良好设计的代码可能比单片函数更难理解,但是重用代码的某些部分并避免代码重复更容易...
显然,通过正确命名对象和函数,代码应该更容易理解。
在上面的代码中,最好忽略函数1和2中的异常并在调用者处理它们。这样,行动就简化了。例如,以下代码更简单但等效:
public int? CalculateSomething(int input)
{
try
{
var result1 = SomeMath(input);
var result2 = SomeOtherMath(result1);
return result2;
}
catch
{
return null;
}
}
并且可以更容易地推广该代码以处理一个操作列表,其中一个函数的结果是下一个函数的输入。