Closures in OOC

接上一篇Complexity Behind Closure,这次来专注于Rock是如何在C里实现Closure的。
这篇文章同时发布在Github上。

Block as Blocks

首先,需要指出的是,在C里面并不是完全没有办法使用Closure。Apple的GCC Fork里就给C添加了Block,用于实现Closure:

(Stolen from Wiki)

#include <stdio.h>
#include <Block.h>
typedef int (^IntBlock)();IntBlock MakeCounter(int start, int increment) {__block int i = start;return Block_copy( ^ {int ret = i;i += increment;return ret;});}int main(void) {IntBlock mycounter = MakeCounter(5, 2);printf("First call: %d\n", mycounter());printf("Second call: %d\n", mycounter());printf("Third call: %d\n", mycounter());/* because it was copied, it must also be released */Block_release(mycounter);return 0;
}
/* Output:First call: 5Second call: 7Third call: 9
*/

除去在代码风格上的偏见后,这个扩展看起来不是很差,不过依然有很多限制,比如考虑下面的功能:

isFound := false
filelist each(|filename, mode|f := func -> String{filename split("/") each(strip()) strip()[-1]}last := f(filename)if(last == "mysdk"){isFound = truereturn true}false
)

尝试这用Apple的block扩展来实现一下?看起来一点不现实。并且就算实现了,也只能在Apple的GCC Fork下面工作,显然并不是我们在追求的东西。

Way to Variable

好的,在开始解释OOC的Closure之前,让我们先来想想Closure要有什么特性:

  • 能够自由的使用“自由变量”。 这里的“自由”是指在语法上定义于Closure以前的变量,比如
void test(Int a){Int b;// closure
}

这时closure要有能力读写变量a和b。当然,这是闭包的基本要求。

  • 能够定义在任何地方。在Object Pascal/Delphi里,虽然我们能够定义nested function,但它的位置并不是自由的——当你在函数中间写了一个nested function时,编译器会要求你把它移动到顶端。但显然很多情况下我们需要的不仅仅是一个“临时”函数,我们还需要一个包含了之前定义过的所有变量的环境。

  • 能够“携带”定义时的环境。考虑下面的情况:

f := func(a: Int) -> Func->Int{b: Int = a*2return func-> Int { a + b }
}f1 := f(3)
f1() toString() println()f1 = f(4)
f1() toString() println()

这个时候应该输出什么? 显然是9和12。但如果我们的闭包不能携带这个环境的话,是没法输出正确的结果的。

Way in OOC

好的,现在让我们来看看在OOC里,closure是怎么实现的,这次让我们从最简单的情况开始:

main: func {a := 3f := func -> Int { a }
}

一个闭包,它只用来返回之前定义过的变量的值。在这里,如果你执行"#{f()}" println()的话,会得到结果3。好的,让我们首先看看OOC生成了什么,然后再慢慢解释: (另外,需要注意为什么我们这里要加上main函数? 因为如果没有main的话,所有的变量都会变成全局,在闭包里本身就可以直接读写,也就失去的例子的意义)

    lang_Numbers__Int a = 3;__simpleclosure_simpleclosure_closure3_ctx* __simpleclosure_ctx4 = lang_Memory__gc_malloc(((lang_types__Class*)__simpleclosure_simpleclosure_closure3_ctx_class())->size);(*(__simpleclosure_ctx4)) = (__simpleclosure_simpleclosure_closure3_ctx) { a};lang_types__Closure __simpleclosure_closure5 = (lang_types__Closure) { simpleclosure____simpleclosure_simpleclosure_closure3_thunk, __simpleclosure_ctx4};lang_types__Closure f = __simpleclosure_closure5;return 0;
}lang_Numbers__Int simpleclosure____simpleclosure_simpleclosure_closure3(lang_Numbers__Int a) {return a;
}lang_Numbers__Int simpleclosure____simpleclosure_simpleclosure_closure3_thunk(__simpleclosure_simpleclosure_closure3_ctx* __context__) {return simpleclosure____simpleclosure_simpleclosure_closure3((*__context__).a);
}

对于简单的两行代码,rock生成了一大堆东西。好的,让我们开始看到底发生了什么。

  1. 首先,OOC里closure是一个结构体,它包含两个部分:contextfunction pointer。function pointer很简单,就如同名字上说的,我们把closure当成一个普通函数,这个函数的指针就是function pointer。而context则包含了所有closure需要的变量。用C的代码来说的话,就像这样:
typedef struct {Closure_Context* context;void (* thunk)(Closure_Context*);
} myclosure;typedef struct {Int a;
} Closure_Context;
  1. 当我们声明一个closure时,首先,这个closure会变成一个普通的函数,假设它的名字是closure_impl,但不同的地方是,除了本身声明时的参数外,这个closure还接受一个Closure_Context*作为参数。随后,我们初始化一个Closure_Context,它的成员包含了所有这个closure里用到的外部变量。在刚才那个例子里,我们只有一个Int a。最后,把这两个指针组合成myclosure,我们的声明就完成了。

  2. 到了这里,相信你早就明白该怎么使用它了,只要简单的myclosure->thunk(myclosure->context),一切就自然的完成了。让我们看看是不是真的这样:

main: func {a := 3f := func -> Int { a }f()
}

编译之后:

    lang_Numbers__Int a = 3;__simpleclosure_simpleclosure_closure3_ctx* __simpleclosure_ctx4 = lang_Memory__gc_malloc(((lang_types__Class*)__simpleclosure_simpleclosure_closure3_ctx_class())->size);(*(__simpleclosure_ctx4)) = (__simpleclosure_simpleclosure_closure3_ctx) { a};lang_types__Closure __simpleclosure_closure5 = (lang_types__Closure) { simpleclosure____simpleclosure_simpleclosure_closure3_thunk, __simpleclosure_ctx4};lang_types__Closure f = __simpleclosure_closure5;((lang_Numbers__Int (*)(void*)) f.thunk)(f.context);

虽然比起手写的代码看起来要复杂,但跟我们的基本思想是完全一致的。

Pain in Context

不过,到这里其实一切并没有结束,因为我有意忽略了一个重要的地方——如何确定我们的context? 很显然,每个closure都有不同的context,不但意味这每个closure的context都要有自己的定义,还意味着我们必须自行推断需要把那些变量放进context里。好吧,让我们先看看第一个问题。

假设我们有这么一个代码:

foo: func{b: Int = 1f := func{b + 1}
}bar: func{a, b: Intf := func{a = 1b = 2}
}

那么根据刚才介绍的构造,每一个f都会变成一个(context,thunk)的集合。thunk没有任何问题——简单的把f写成函数取地址就够了,那么让我们想想context该怎么构造。如果直接写的话,那么会是这样:

typedef struct{int b;
} foo_f_context;typedef struct{int* a;int* b;
} bar_f_context;

这样,我们可以通过foo_f_context* -> b来访问b(只读),而可以通过bar_f_context* -> a来读写变量a。对,这种最简单的方法就是OOC所采用了。原因很简单——OOC不是C,在编译代码时所有closure访问了哪些变量都已经确定下来,我们只需要扫描一次,然后生成对应了struct就足够了。因此你会在头文件里找到这样的定义:

struct ___simpleclosure_simpleclosure_closure3_ctx {lang_Numbers__Int a;
};

因为一切都是自动生成的,我们并不担心会出错。并且,这种方式非常简单而且高效。不过,如果打算直接在C代码里使用闭包,那问题就要复杂的多了——毕竟我们不可能给每一个闭包手写一个结构体。纵使可以用Hashmap来自由的访问变量,如何收集变量是一个大问题。现在回头看来,似乎Apple挑选了一个最好的方式,在没有破坏C的结构之下引入了闭包。

转载于:https://www.cnblogs.com/akisan/p/4296075.html

Closures in OOC相关推荐

  1. python findall函数用法_Python--re模块的findall等用法

    1)正则表达式含义 . # 点可代表一切字符 \ # 起转义作用 [...] # 指代方括号中的任意字符 \d # 指代数字0-9 \D # 指代非数字 \s # 指代一切空格,包括tab制表符.空格 ...

  2. C#和Java的闭包-Jon谈《The Beauty of Closures》

    第一段略... 大多数讲闭包的文章都是说函数式语言,因为它们往往对闭包的支持最完善.当你在使用函数式语言时,很可能已经清楚了解了什么是闭包,所以我想写一篇在经典OO语言出现的闭包有什么用处应该也是很合 ...

  3. 闭包(Closures)

    闭包是自包含的函数代码块,可以在代码中被传递和使用.Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似. 闭包可以捕获和存储其所 ...

  4. JavaScript 小记 之 闭包(Closures)

    Closures are functions that refer to independent (free) variables. 闭包是以静态方式/词法方式进行存储所有父作用域的一个函数 在Jav ...

  5. Swift之深入解析闭包Closures的使用和捕获变量的原理

    一.Closures 简介 ① 什么是闭包? 闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值.Swift 中的闭包与 C 和 Objective-C 中的代码块(b ...

  6. 深入理解JavaScript系列:闭包(Closures)

    介绍 本章我们将介绍在JavaScript里大家经常来讨论的话题 -- 闭包(closure).闭包其实大家都已经谈烂了.尽管如此,这里还是要试着从理论角度来讨论下闭包,看看ECMAScript中的闭 ...

  7. Swift 中的Closures(闭包)详解

    Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中, ...

  8. vivado中的OOC技术

    一.什么是OOC OOC(Out-of-context)是Vivado提供的一项技术,选择将HDL对象当作一个隔离模块运行,完成自底向上的综合流程. 底层的OOC模块相对于顶层模块独立运行,并且拥有自 ...

  9. Swift 06.Closures

    Closures --闭包 看了好些文章.由于自己也是刚开始学习swift,闭包还是不是很明白.暂时先放放.等看完后面的.加深感触后,在回头总结闭包的概念. 数组中常用的闭包函数 在Swift的数组中 ...

  10. 【Swift 60秒】51 - Closures as parameters

    0x00 Lesson Because closures can be used just like strings and integers, you can pass them into func ...

最新文章

  1. [转]cocos2d-js 3.0 屏幕适配方案 分辨率适应
  2. 四川托普计算机职业学校里能拿什么快递,四川托普计算机职业学校怎么样_招生问答...
  3. shell之sed 记录
  4. java:不同数据类型的转换规则
  5. C++中的内存管理(new、delete、内存泄漏)
  6. oracle vm virtualbox右ctrl切换显示模式
  7. 微信公众账号开发文档
  8. 【BZOJ5213】[ZJOI2018]迷宫(神仙题)
  9. 华为nova 9系列曝光:全系标配骁龙778G 4G处理器
  10. Floyed-Warshall算法(求任意两点间最短距离)
  11. [UnityShader基础]06.#pragma multi_compile
  12. DevOps使用教程 华为云(14)持续集成 流水线 自动化测试 怎么用
  13. DPDK框架原理简介 (0002转)
  14. java对象赋值_Java对象引用和对象赋值
  15. display函数怎么使用_使用Python写一个小游戏alien invasion!
  16. 运维标书技术部分的编写
  17. 移动硬盘驱动怎么修复_为什么您的新硬盘驱动器未在Windows中显示(以及如何修复)...
  18. 运动控制 轨迹规划综述
  19. IIS7.5 500.19的解决方法 错误代码 0x8007007e
  20. 百度收录排名好的网站-哪些网站百度收录排名好

热门文章

  1. Java入门,最全面最简单的Java基础教程
  2. 国产高可用软件,双机热备与冷备的区别
  3. 如何看计算机几核,如何查看电脑CPU是几核的?,这几步你要了解
  4. 6-1 哈夫曼树及哈夫曼编码
  5. WCF学习之旅—WCF第二个示例(五)
  6. 实现多方数据安全共享,解决普惠金融信息不对称难题
  7. oracle latch chain,ORACLE latch AND mutex 深入讲解
  8. 颜色拾取器和红蜻蜓截图
  9. mysqlError: Can't connect to MySQL server on 'localhost' (10061)
  10. C语言求卢卡斯序列,斐波那契序列和卢卡斯序列