C# 连续传递样式中的中间值和返回值
我来自一个面向对象的、非功能性的背景,所以我很难完全直观地看到几个有关的在线示例。此外,Scheme之类的函数式语言不必指定参数类型或返回值,因此我不确定我的想法是否正确 由于C#支持lambdas,我从Wikipedia文章中选取了第一个示例,并尝试使用强类型将其移植到C#,以了解该模式将如何应用:C# 连续传递样式中的中间值和返回值,c#,lambda,scheme,continuations,continuation-passing,C#,Lambda,Scheme,Continuations,Continuation Passing,我来自一个面向对象的、非功能性的背景,所以我很难完全直观地看到几个有关的在线示例。此外,Scheme之类的函数式语言不必指定参数类型或返回值,因此我不确定我的想法是否正确 由于C#支持lambdas,我从Wikipedia文章中选取了第一个示例,并尝试使用强类型将其移植到C#,以了解该模式将如何应用: // (Scheme) // direct function (define (pyth x y) (sqrt (+ (* x x) (* y y)))) // rewriten with
// (Scheme)
// direct function
(define (pyth x y)
(sqrt (+ (* x x) (* y y))))
// rewriten with CPS
(define (pyth& x y k)
(*& x x (lambda (x2)
(*& y y (lambda (y2)
(+& x2 y2 (lambda (x2py2)
(sqrt& x2py2 k))))))))
// where *&, +& and sqrt& are defined to
// calculate *, + and sqrt respectively and pass the result to k
(define (*& x y k)
(k (* x y)))
因此,在C#中重写CPSpyth&
版本导致:
// (C#6)
// continuation function signature
delegate double Cont(double a);
// *&, +& and sqrt& functions
static double MulCont(double a, double b, Cont k) => k(a * b);
static double AddCont(double a, double b, Cont k) => k(a + b);
static double SqrtCont(double a, Cont k) => k(Math.Sqrt(a));
// sqrt(x*x + y*y), cps style
static double PythCont(double x, double y, Cont k) =>
MulCont(x, x, x2 =>
MulCont(y, y, y2 =>
AddCont(x2, y2, x2py2 =>
SqrtCont(x2py2, k))));
我本可以使用泛型而不是double,但签名会更长。无论如何,我不确定的是:
Cont
签名是否正确(即Func
)?如果继续fn。接受参数,处理它,然后返回相同类型的值sqrt&
,所有其他调用都会得到lambda,而lambda并不会真正“传递”中间值给原始continuation。上面函数中的代码基本上类似于k(Math.Sqrt(x*x+y*y))
,这是否意味着我关于中间“挂钩”的假设是错误的(define (pyth& x y k)
(*& y y (lambda (y2)
(*& x x (lambda (x2)
(+& x2 y2 (lambda (x2py2)
(sqrt& x2py2 k))))))))
这就是问题所在
在一些进一步的应用程序中,正如guys在评论中所说的,转换到CPS会将每个应用程序移动到尾部位置,因此调用堆栈将被替换为lambdas,而且,如果将它们去功能化,会得到一个表示控制流的数据结构——一个要转换为的整洁表单,比如C,或者其他命令式语言。完全自动化!
或者,如果您想实现一些monad mumbo jumbo,比如说monad,在CPS中很容易,只需在每个延续lambda之前添加一个测试,测试接收的值是否“只是某个东西”(在这种情况下,执行作业并将结果推送到您的延续),或者“什么都没有”,在这种情况下,您只需推什么(到延续lambda)。
当然,要用另一个程序或宏,而不是手工,因为这可能会很乏味——cps最神奇的地方在于,它很容易自动转换为cps
希望我没有让它变得不必要的复杂。我已经对延续单子做了一个非常全面的介绍,你可以在这里找到 你也可以找到一把.Net小提琴 我在这里概括地重复一遍 从初始函数开始
int Square(int x ){return (x * x);}
publicstaticvoidsquare(intx,动作回调)
{
回调(x*x);
}
public static Action Square(int x)
{
return(callback)=>{callback(x*x);};
}
public static Func Square(int x)
{
return(callback)=>{callback(x*x);};
}
代表T Cont(函数f);
公共静态继续(此U x)
{
返回(回调)=>回调(x);
}
正方形。继续()
publicstaticcontbind(
本节,
Func k,
Func选择器)
{
返回(函数c)=>
m(t=>k(t)(y=>c(选择器(t,y)));
}
我觉得很好。唯一的问题是它应该使用尾部调用。虽然这个例子很好,但是你的堆栈会随着更多的代码迅速膨胀。如果不知道你对钩子的直觉是什么,很难回答这个问题。使用CPS,被调用方可以多次调用延续,或者根本不调用,或者将其保存在某个地方以供以后调用。这个例子是fa
int Square(int x ){return (x * x);}
public static void Square(int x, Action<int> callback)
{
callback(x * x);
}
public static Action<Action<int>> Square(int x)
{
return (callback) => { callback(x * x); };
}
public static Func<Func<int,T>,T> Square<T>(int x)
{
return (callback) => { callback(x * x); };
}
delegate T Cont<U, T>(Func<U, T> f);
public static Cont<U, T> ToContinuation<U, T>(this U x)
{
return (callback) => callback(x);
}
square.ToContinuation<Func<int, int>, int>()
public static Cont<V, Answer> Bind<T, U, V, Answer>(
this Cont<T, Answer> m,
Func<T, Cont<U, Answer>> k,
Func<T, U, V> selector)
{
return (Func<V, Answer> c) =>
m(t => k(t)(y => c(selector(t, y))));
}