抽象之艰难,一次作业分析

1. 问题的提出,一次作业

要求实现 小学四则运算系统,系统出10道题,用户输入答案,系统判定对错,系统给出对错的数量。

这是以邹欣老师的《构建之法》作为教材,某高校的软件工程课程作业。

该作业要求迭代开发,在《构建之法》群里,企业的工程师、高校教师对某次某位同学提交的作业进行了点评。

这位同学提交的是 JAVA GUI 版本的程序。控制件的选择,每道题的两个数是文本框禁用,要求用户输入答案处是文本框,判断对错是文本框禁用;选择加减乘除题目,是按钮。

该同学实现了出10道题目。问题来了,邹欣老师问,如果我要求出40道题目呢?之所以有此一问,是因为该同学硬编码了10道题目的 控件和业务逻辑,她的代码看起来是这样的。

----节选代码开始

JTextField jfirst1, jfirst2, jfirst3, jfirst4, jfirst5, jfirst6,
jfirst7, jfirst8, jfirst9, jfirst10;// 10道题的分别的第一
个数据JLabel jsymbol1, jsymbol2, jsymbol3, jsymbol4,
jsymbol5, jsymbol6,
jsymbol7, jsymbol8, jsymbol9, jsymbol10;// 10道题的分别的符号
JTextField jsecond1, jsecond2, jsecond3, jsecond4, jsecond5, jsecond6,
jsecond7, jsecond8, jsecond9, jsecond10;// 10道题的分别的第二个数据
JTextField janswer1, janswer2, janswer3, janswer4, janswer5, janswer6,
janswer7, janswer8, janswer9, janswer10;// 10道题分别的结果

// 添加每道题的符号标签
jsymbol1 = new JLabel("?");
jsymbol2 = new JLabel("?");
jsymbol3 = new JLabel("?");
jsymbol4 = new JLabel("?");
...
// 设置答案对错的标签
jjudge1 = new JLabel("?");
jjudge2 = new JLabel("?");
jjudge3 = new JLabel("?");
jjudge4 = new JLabel("?");
...

/* 加法计算 */
if (e.getSource() == AddBtn) {
flag=1;
jsymbol1.setText("+");
jsymbol2.setText("+");
jsymbol3.setText("+");
....
fun();
random();
}

// 计算第二道题
String answer2 = null;
a2 = first2 + second2;
answer2 = Double.toString(a2);
String result2 = janswer2.getText();
if (result2.equals(answer2)) {
jjudge2.setText("对");
true1 = true1 + 1;
jjright.setText(Integer.toString(true1));
} else {
jjudge2.setText("错");
jjresult2.setText(answer2);
}
// 计算第三道题
// 计算第四道题

----节选代码结束

2. bad smell

该同学的全部代码在这里,java语言GUI版本的[http://www.cnblogs.com/tujiangfeng/p/4480053.html]。

事实上,能细心地安置10个控件,复制粘贴->修改其中的不同,是件很不容易的事情。需要耐心和精确,稍有不慎就会操作错误,而查找这个错误非常困难,因为它隐藏在面目相似的群体之中。又,耐心和精确,确是一种非常可贵的品质,难以替代,不过,科学和技术还给我们另一种力量,就是让 平凡的人 拥有更强大的力量。任何人都能学会,只要掌握就能更牛,这是科学与巫术的不同。

该同学的问题,在她稍晚的作业中自己回答了,"Duplicated Cod(重复代码)" [http://www.cnblogs.com/tujiangfeng/p/4500116.html],这是重要的代码坏味道,重要到,它排在了坏味道中的第一名。

所谓重复,就是看起来 它们相似。

该同学其实曾经完美的解决过这个问题,在她更早的作业中,C语言console版本的 [http://www.cnblogs.com/tujiangfeng/p/4399804.html],她用for循环实现了5道题目,判断题目对错,只写了一次代码, (而在JAVA GUI版本中,她写了10次)。

3. 解决之道

沉默的代码(某位资深工程师,他在群里不是这个名字,所以不知道是谁)在作业的博客中批阅道,"使用For循环重构上面的代码"。

那天,在群里得有10位左右资深工程师给出了相同的意见,用了各种不同而相似的说法。相信该同学已经蒙了,"他们到底说的是什么啊。"最打击人的,不少资深工程师对自己给出的方法加了限定和修饰,"这是非常基本的方法"。

如果该同学看到我这篇博客,请一定仔细读一下我下面这一段话。"基本"一词,不是指它简单,或者暗示你在道德 (勤奋、努力之类的)上 理应掌握,而是它极其经常在工业中应用,所以,需要掌握。需要掌握的原因,是这是本领域的"基本"技术,就像战士的射击。

资深工程师们的核心思想是,用循环去除重复。

该同学的疑问有二。一,她们没学过数组控件。对此,邹欣老师建议,用控制台实现,用意大概是先掌握抽象这一方法,至于控件什么的技术,倒在其次。该同学提到,JAVA没有学过控制台的程序。二,怎么循环呢,怎么抽象呢?

对于第二个疑问,即需要A.原则,B.精晰的可操作的指令,也需要C.示范。示范在后面,下面先给出指令。

把上述代码抽象为循环,A.原则是,找出代码中 相同的部分 和 有变化的部分。把相同的部分放在循环 (或者函数)中,把不同的部分,作为循环的变量 (或函数的参数。参数又译为变数,其义正对),或者作为变量的函数 (这个函数的意思是数学上的,即可以由变量求得)。

B. 指令如下:

把那些名字相似,只有最后一个数字不同的,不单独声明为变量,而是声明为数组;不用名字区分它们,而是使用数组的下标区分它们。

凡是对这些变量操作(读或写)的,改为对数组元素操作。对某元素操作的若干次,改为在循环中执行,每次变更 (比如递增1) 数组元素的下标。

一共三种代码需要关注。(1)不变的部分,在每次循环中都一样, (2)数组下标,每次循环都变更, (3)下标或循环变量可能不同,需要找到它们间的关系。本例中没有第 (3)种情况。

C. 示范,上面的代码,按资深工程师们的指示,大概改成下面这个意思。

----节选代码开始
List<JTextField> jfirst = new ArrayList<JTextField>();  // 10道题的分别的第一个数据
List<JLabel> jsymbol = new ArrayList<JLabel>();         // 10道题的分别的符号
List<JTextField> jsecond = new ArrayList<JTextField>(); // 10道题的分别的第二个数据
List<JTextField> janswer = new ArrayList<JTextField>(); // 10道题分别的结果

// 添加每道题的符号标签
// 设置答案对错的标签
int max_length = 10;
for(int i = 0; i< max_length; ++i)
{
jsymbol.add(new JLabel("?"));
jjudge1.add(new JLabel("?"));
}

...

/* 加法计算 */
if (e.getSource() == AddBtn) {
flag=1;
for(int i = 0; i< max_length; i++)
{
jsymbol[i].setText("+");
}
fun();
random();
}

// 计算第二道题
// 计算第三道题
// 计算第四道题
for(int i = 0; i< max_length; ++i)
{
String answer[i] = null;
a[2] = first[2] + second[2];
answer[2] = Double.toString(a[2]);
String result[2] = janswer[2].getText();
....//省略的原因不是像循环的类似,而是可按上述方法修改
}
----节选代码结束

4. 不仅如此

对于教学本身的讨论,至此可以告一段落,但是更深的讨论,才刚刚开始。在经典导论教材SICP中,一共五章的书里,用了三章讲抽象,甚至标题就叫做XX抽象。可见抽象之重要。

抽象为循环、抽象为函数,以上给出的原则、指令、示范,是非常形而下的,因此更容易操作。但是,在何处情况下应该做抽象,如何抽象,是个更深刻的问题。比如,我们是否要把四则运算中的加减乘除抽象为同一个函数么,向然后向这个函数传入一个 first class 的操作符;我们是否要把供用户选择题目类型的按钮抽象为一个数组;为什么要这样做,为什么不这样做?为什么不抽象为面向对象,为什么不抽象为AOP,为什么不使用设计模式。

如果仅回答,"使用这些技术会导致代码更复杂",那么,"复杂"在这里,很大程度上只是个人的主观体验。这位同学也可以说,抽象成控件数组,还不如复制粘贴容易呢。在做项目的时候,我的学生就真的这样质疑过。当然,我们可以回答,"当代码规模更大的时候",可是,你又如何知道代码的成长方向呢?

从一次次的执行 (执行,是动态,或复制粘贴,是静态)中,发现其中不变的部分,抽象这些部分,并允许不同的部分可以在不同的情境 (第几次循环,函数的参数)下指定;还要在代码中同时包括不变和变化的部分 (比如,用循环变量和数组下标表示变化)。

这是一项异常艰难的工作。因此,教授抽象,也非常艰难,如果不是更难的话。

刘典说,他是先学会编程,后学的英语。因此,在他看来,for的第一含义就是循环,而不是"为了"。对于工程师而言,把技术内化为本能,值得称道,不过,作为教师,这就是 (如他所说)知识诅咒。因为你忘记了当初如何艰难地掌握了这一点,甚至,你并没有深刻掌握,只是每次都类比过去的成功经验,照样应用了而已。

为什么艰难?因为在编程以外的经验中,我们鲜能发现 循环。有人可能会说,日出而作、日落而怎,每个学期、每周的课表,这不是循环吗?这不是。这不是实现完整的循环所需要的,是粗糙的。在刚刚提到的这些循环中,什么是循环变量,循环的开始和终止条件是什么,有必要存在循环变量吗,是否可以用迭代器取代,用foreach呢,在循环体中,哪些是每次循环要改变的,哪些是不变的。

有人说,这简单啊,上述问题都能答上来。但是,你在日常生活中考虑上述循环的时候,会考虑这些问题吗,还是只是粗糙地应付过去?这就是日常生活中没有循环这一经验的原则。

而在掌握一项技能之初,有类似的经验非常重要。这就是为什么物理学的力学部分,男同学普遍比女同学掌握快的原因--经验。这也是为什么物理学的电学部分,好多同学一下子就蒙了的原因,不是因为看不见摸不着,而是现实中没有这样的东西。水流、水压,这些比喻并不与电学概念完全对应,更何况大家对这些流体 (静?) 力学的经验也就呵呵。

循环的经验来自于哪里,为什么循环对很多人难以掌握?

与循环高度类似的,是高中代数中的数学归纳法,是数列的通项,是求得的sigma上面的那个i。而这些,有很多人本来学得就烂。这就是循环的抽象。当然,前面学得不好,也可以利用计算机的for循环学习这种抽象。

(题外话,类似的,与实验技能相关的,实验现象说明什么,假阴性假阳性,这些对应的是初中平面几体里的充分条件必要条件。很多人没有把逻辑推理内化,或者没有自觉地使用这一工具,因此在做实验的时候会糊涂。)

为什么抽象,如何抽象,这个话题也可以延伸到面向对象的继承和多态,不赘述。我想说的是,抽象,并不是简单和自然的,不是理应掌握的技能,而是必须掌握的技能。
e63cb2af6c80c8c3321842560ade5334_b
-------

博客会手工同步到以下地址:

[http://zhuanlan.zhihu.com/younggift]

[http://giftdotyoung.blogspot.com]

[http://blog.csdn.net/younggift]

这样的写法 (function(){ /* do something */ })(); 的含义, JavaScript

执行匿名函数,并且匿名函数中的变量不污染全局变量空间。

代码:

  1. <html>
  2. <head>
  3. <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  4. </head>
  5. <body>
  6. <script type="text/javascript">
  7. (function(){
  8.     var inside="inside";
  9.     document.writeln("<p>函数0 无返回值,运行,未污染全局变量空间<br>");
  10.     document.writeln("I am running in a function anonymous.<br>");
  11.     document.writeln("var inside undefined :" + (typeof inside === 'undefined') + "<br>");
  12.     document.writeln("var inside values: " + inside + "<br>");
  13. })();
  14. var res1 = (function () {
  15.     var inside="inside";
  16.     document.writeln("<p>函数1 有返回值,运行,未污染全局变量空间<br>");
  17.     document.writeln("I am running in a function anonymous.<br>");
  18.     document.writeln("var inside undefined :" + (typeof inside === 'undefined') + "<br>");
  19.     document.writeln("var inside values: " + inside + "<br>");
  20.     return 111;
  21. })();
  22. var res2 = function () {
  23.     var inside="inside";
  24.     document.writeln("<p>函数2 有返回值,运行,未污染全局变量空间<br>");
  25.     document.writeln("I am running in a function anonymous.<br>");
  26.     document.writeln("var inside undefined :" + (typeof inside === 'undefined') + "<br>");
  27.     document.writeln("var inside values: " + inside + "<br>");
  28.     return 222;
  29. }();
  30. var res3 = function foo() {
  31.     var inside="inside";
  32.     document.writeln("<p>函数3 有返回值,运行,未污染全局变量空间<br>");
  33.     document.writeln("I am running in a naming function.<br>");
  34.     document.writeln("var inside undefined :" + (typeof inside === 'undefined') + "<br>");
  35.     document.writeln("var inside values: " + inside + "<br>");
  36.     return 333;
  37. }();
  38. function bar() {
  39.     var inside="inside";
  40.     document.writeln("<p>函数4 有返回值,定义未运行,未污染全局变量空间<br>");
  41.     document.writeln("I am running in a naming function.<br>");
  42.     document.writeln("var inside undefined :" + (typeof inside === 'undefined') + "<br>");
  43.     document.writeln("var inside values: " + inside + "<br>");
  44.     return 444;
  45. };
  46. var res4 = bar();  // bar运行
  47. document.writeln("<p>全局,所有匿名函数未污染全局变量空间<br>");
  48. document.writeln("var inside undefined :" + (typeof inside === 'undefined'));
  49. document.writeln("<br>");
  50. document.writeln("function foo undefined :" + (typeof foo === 'undefined'));
  51. document.writeln("<br>");
  52. document.writeln("function bar undefined :" + (typeof bar === 'undefined'));
  53. document.writeln("<br>");
  54. document.writeln("匿名函数1返回值:" + res1);
  55. document.writeln("<br>");
  56. document.writeln("匿名函数2返回值:" + res2);
  57. document.writeln("<br>");
  58. document.writeln("命名函数foo定义并调用的返回值:" + res3);
  59. document.writeln("<br>");
  60. document.writeln("命名函数bar调用的返回值:" + res4);
  61. document.writeln("<br>");
  62. </script>
  63. </body>
  64. </html>

执行结果:

  1. 函数0 无返回值,运行,未污染全局变量空间
  2. I am running in a function anonymous.
  3. var inside undefined :false
  4. var inside values: inside
  5. 函数1 有返回值,运行,未污染全局变量空间
  6. I am running in a function anonymous.
  7. var inside undefined :false
  8. var inside values: inside
  9. 函数2 有返回值,运行,未污染全局变量空间
  10. I am running in a function anonymous.
  11. var inside undefined :false
  12. var inside values: inside
  13. 函数3 有返回值,运行,未污染全局变量空间
  14. I am running in a naming function.
  15. var inside undefined :false
  16. var inside values: inside
  17. 函数4 有返回值,定义未运行,未污染全局变量空间
  18. I am running in a naming function.
  19. var inside undefined :false
  20. var inside values: inside
  21. 全局,所有匿名函数未污染全局变量空间
  22. var inside undefined :true
  23. function foo undefined :true
  24. function bar undefined :false
  25. 匿名函数1返回值:111
  26. 匿名函数2返回值:222
  27. 命名函数foo定义并调用的返回值:333
  28. 命名函数bar调用的返回值:444

JavaScript语言实现类似这样的效果: 3.grams.flour

JavaScript语言实现类似这样的效果: 3.grams.flour

参考 Javascript: The Good Parts,第33页

代码如下:
<html>
<body>

<script type="text/javascript">

// copied from page 33 of Javascript: The Good Parts
Function.prototype.method = function (name, func)
{
this.prototype[name] = func;
return this;
}

Number.method('chi', function() {
return this*0.333;
});

document.writeln( (3).chi());
document.writeln( "<br>" );
document.writeln( (3+1).chi() );
document.writeln( "<br>" );
document.writeln( 3.2.chi() );
document.writeln( "<br>" );
document.writeln( 3.0.chi() );
// document.writeln( 3.chi() );
// SyntaxError: identifier starts immediately after numeric literal

</script>

</body>
</html>

运行结果如下:

0.9990000000000001
1.332
1.0656

0.9990000000000001

努力学习,苦中作乐。

笔记:javascript通过反射和遍历,访问对象的所有属性,及 按引用/按值传递

正读 JavaScript: The Good Parts.

1. 通过反射和遍历,访问对象的所有属性。
<html>
<body>
<script type="text/javascript">
var x = {
"first-name": "Gift",
"last-name" : "Young"
};
x.nickname = 'nick';
for(name in x){
document.writeln(name+":"+x[name]+" "+typeof x[name]+"<br>");
}
</script>
</body>
</html>
上述代码的运行结果是:
first-name:Gift string
last-name:Young string
nickname:nick string

2.  " Objects are passed around by reference. They are never copied. "

下面的试验证实,String是按值复制的。
x与stooge是同一对象的reference,所以它们的属性的值一起变化;
snick和xnick的值“来自”对象的属性,但是snick和xnick的值并不随对象的属性的值的变化而变化。
<html>
<body>
<script type="text/javascript">
var stooge = {
"first-name": "Gift",
"last-name" : "Young"
};
var x = stooge;
x.nickname = 'before';
var snick = stooge.nickname;
var xnick = x.nickname;
document.writeln("nick:"+stooge.nickname+"<br>");
document.writeln("x.nickname:"+x.nickname+"<br>");
document.writeln("xnick:"+xnick+"<br>");
document.writeln("snick:"+snick+"<br>");
document.writeln("<br>");
x.nickname = 'after';
document.writeln("nick:"+stooge.nickname+"<br>");
document.writeln("x.nickname:"+x.nickname+"<br>");
document.writeln("xnick:"+xnick+"<br>");
document.writeln("snick:"+snick+"<br>");
</script>
</body>
</html>

上述代码的运行结果是:
nick:before
x.nickname:before
xnick:before
snick:before
nick:after
x.nickname:after
xnick:before
snick:before

努力学习语法,勿将高塔起浮沙。

系统边界,不可逾越

有不少同学在读我的博客的时候,都先预设一下,这一篇是技术的呢,还是生活或扯淡的呢?其实二者并无明显的界线,如果我们不能使用相同的原则处理生活和工作,那世界该复杂到如何可怕的程度啊。

昨天新闻,长春154公交车上,一女子因为睡着了没有给老人让座,被老人殴打。细节从略。在十年多前,对这样新闻的态度将是一边倒地批评女子--怎么能不尊老爱幼呢,怎么能忍心让老人站着呢。现在反面的声音开始取得了强势地位--不是老人们变坏了,而是坏人们变老了。

就跟评判合同纠纷一样,我们应该先抛开善恶 (低于道德,或超出道德) ,来看看系统边界。否则,我们就特别容易陷入一个特别中国式的误区:要么这个家伙是个圣人,要么这个家伙是个恶棍,二者必居其一;反正正常人类是不存在的。

系统边界,是先天的或约定的,系统与系统之间的隔离。当系统A与系统B交流时,系统A可以根据系统边界的契约 (或先天的)预期系统B的行为。

在软件工程中,系统边界是整个生命周期的第一步。在自然科学中,确定问题(也就是边界)是第一步。边界是铁笼,是孙悟空给唐僧画的圈儿,绝不对逾越。

有同学说,那逾越了呢,唐僧不就跨过去了么。逾越边界会出事,比如被白骨精吃掉,或者预料之外的代价--我们应该把孙悟空被咒的痛苦作为一个变量考虑在内,而不能漠不关心。逾越边界意味着一件最大的事情发生了,即我们换了一个问题,或者说,我们换了一个系统。

争论的时候,如果双方发现讨论的根本不是一个问题,或者范围不同,就应该立即停止争论,重新界定范围。是一个道理。

明白了这个道理,就很容易理解下面这些事。

1.开车的司机说,非机动车道我占一下又如何,反正现在也没有车--ZHUMAO说,实线就是墙,压线就是撞墙。他把人的法则转换为自然的铁律来理解,容易多了。

2. 楼道里堆点东西又如何--不必讨论在消防事故万一发生时的危险,那不是你自己家的地方,任何对公共资源的不可共享的侵占,哪怕只有一秒,统统都是越
界的。

3. 素食的人评论 吃肉的也可以是好人,但是不吃肉的更好。如果你能用某个标准来评论别人,也意味着你超越了每个个体间的界限,并正在以道德实加压力。这与"我岁数大,你得给我让座"没什么区别。

从评论别人素食这一点说开去,你当然有表达的权力,但是绝无要求别人按你的期待回应的权力。如果道德附加奖惩措施,就越过了道德的边界,而开始扮演法律的角色。从不吃肉的更是善人,到殴打不给让座的乘客到浸猪笼,只有一步距离。这样的例子在我们的传统文化和反映了我们传统文化的故事里,比比皆是:师父废徒弟武功,清理门户,决定婚姻,人身依附。

所以,以素食为例,你不能说我"不是更好",甚至不应该评论"不吃肉的更好",正如我也没有恶意评论"吃肉的智商更正常一些"。当我尊重你的选择的时候,也麻烦你自觉一点。你别说我不善良,我也不说你傻;你别说某些人更善良,我也不说你以外的那些更聪明。如果你胆敢越过边界一步,看我不斩断你的手指。

有同学问我,"老师,你也不信基督教,为什么会了解这么多基督教的事儿呢。"说实话,攒这些东西,就是等你们向我传教的时候,心情不好用于反击的。我都没给你们讲共产主义什么的,你给我讲什么基督或者佛或者孔子的理想。大家相安无事,仅在世俗的世界里严守边界,不好么。

严守边界。边界有些是法律或先天约定的,有些是甚至你我的契约的。有同学说,契约不能变更么?当然能。

契约变更的要求是: 按契约要求的条款赔偿。最近看来些事件和评论,有人认为在契约破裂的时候为争取自己的权益 (不是权利)而采取些手段 (比如欺骗)是自然的。这个"自然的",就跟"我也不能咋咋样"啊一样,都是非常扯淡的。

看你是个什么样的人,不取决于你如何对付朋友,恰恰取决于你如何对待陌生人,如何对待敌人。

严守边界。这就是我们应该做的,我们唯一能做的。

契约边界的底线,就是字面上的意义。契约当初如何约定的,无比喻地、无抒情地兑现。

有的同学会说,这么简单?对,就这么简单。甚至连这么简单的,很多人都做不到。这就是为什么高晓松醉酒驾车,其错在先,却很多人为他服刑叫好,说是真男人。能不折不扣地兑现你早就想毁掉的契约并不容易。

什么情况下可以越界呢?在别人同意的前提下,付出你更多的利益,这是可以的。对别人好,为什么需要别人同意呢。因为你也不知道别人是否觉得这是他想要的,你没有权利和能力替别人选择。

当你付出利益的时候,还要注意一件事,就是不能连带把不属于你的利益也付出去。当年有官员裸捐,我到现在没有想明白,明明财产是他和他的妻子的,他一个人做得了主么?颇有些人认为能做得了自己妻子的主是男人范,其实不然,这跟打老婆或者用老婆还赌分明就是一个水平线上的。不需要太多地说理,只要考虑到你的老婆 (这一角色只是她社会角色中的一个)也是与你同样的人类,就够了。

此外,契约所产生的边界,除非另有约定,不可让度。什么意思呢?前高法副院长说,有麻烦不要找法院,要先找德高望重的邻里论断一下。

法国人的观点是,国家与公民之间,再无中介。公民唯一赋予国家的暴力权力和裁决权,不可让度与"德高望重的邻里"。这就跟初中小男生送给你这位梦中情人的玫瑰花,你再觉得它适合送给你的梦中情人,也不能把这一朵送出去。你可以拒绝,却不能转让。

上面说了这么多,边界都是由契约构造出来的。我一直隐约提到除契约以外,还有来自先天的边界。那就是一个人的自由,不可舍弃,不可让度,不可侵犯。关于自由这一边界,一个非常典型的例子是欺骗,我们下回再说。

------------------------------------------------------------

博客会手工同步到以下地址:

[http://giftdotyoung.blogspot.com]

[http://blog.csdn.net/younggift]

C#实现可变数量参数,像printf那样

C语言的printf函数,参数的数量可以是 可变的。使用 va_start 和 va_end  实现。

C++中,实现参数可变,可以用 overload。如果参数数量不是很多的话,可以一个参数实现一个函数,两个参数再实现一个函数,三个参数再实现一个函数...

或者使用C的方案。

C#中,也可以用 overload。或者 params 关键字。

下述代码除了演示 params 实现变数量的参数,还演示了 这些参数的类型不必相同。

下述代码的输出是

  1. 1
  2. 2
  3. one
  4. two
  5. aa
  6. one
  7. two
  8. aa
  9. 1
  10. 2

代码如下。

  1. namespace @params
  2. {
  3.     class Program
  4.     {
  5.         public string value = "aa";
  6.         static void Main(string[] args)
  7.         {
  8.             Program p = new Program();
  9.             foo(1, 2, "one""two", p);
  10.             System.Console.WriteLine();
  11.             foo("one""two", p, 1, 2 );
  12.         }
  13.         public static void foo(params object[] list)
  14.         {
  15.             foreach(object o in list)
  16.             {
  17.                 if (o.GetType() == typeof(int))
  18.                 {
  19.                     System.Console.WriteLine(o);
  20.                 }
  21.                 else if (o.GetType() == typeof(string))
  22.                 {
  23.                     System.Console.WriteLine(o);
  24.                 }
  25.                 else if (o.GetType() == typeof(Program))
  26.                 {
  27.                     System.Console.WriteLine(((Program)o).value);
  28.                 }
  29.             }
  30.         }
  31.     }
  32. }

C#语言实现类似这样的效果: 3.grams.flour

C#语言实现类似这样的效果:  3.grams.flour
参考 Martin Fowler《领域特定语言》第387页的代码,我做如下实现。
3.chi(), 三尺 转换为国际标准单位制米。

  1. namespace int_ext
  2. {
  3.     class Program
  4.     {
  5.         static void Main(string[] args)
  6.         {
  7.             System.Console.WriteLine( 3.chi() ); // output 0.999
  8.         }
  9.     }
  10.     public static class int_e
  11.     {
  12.         public static double chi(this int arg)
  13.         {
  14.             return arg * 0.333;
  15.         }
  16.     }
  17. }

闭包在DSL中的应用,领域特定语言的笔记

章节名:闭包
页码:第319页 2015-01-16 14:27:41

1.
各种语言称谓不同。
C语言可以用带有 void* 参数存储变量引用的 函数指针实现闭包。

2.问题的提出:用对象作为谓词,语法麻烦。

3.实例:C#2.0 & C#3.0

4.
总结闭包:
4.1 变量引用,而非复制,lexical scope;
4.2 lazy eval;
4.3 闭包的创建、保存、执行。

Fwd: 百度地图坐标偏移的纠正

百度地图坐标偏移的纠正

1. 问题

在使用百度地图开发时,输入经纬度 (用google earth或GPS设备得到) ,会发现坐标显示位置偏移。

比如,已知东北师范大学经纬度 125.3249352, 43.8593245。经纬度可以通过地理科学学院门口的雕塑上查到,可以通过google earth查到,可以通过能显示经纬度的GPS设备,或者用iphone在东北师大拍张照片,然后用带有识别exif的工具比如 google picasa查到。

在下述代码1中,我们发现,标记 125.3249352, 43.8593245 的位置不在东北师大,而是偏移到了南湖里。

-----代码1开始--------

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body, html,#allmap {width: 100%;height: 100%;overflow: hidden;margin:0;}
#l-map{height:100%;width:78%;float:left;border-right:2px solid #bcbcbc;}
#r-result{height:100%;width:20%;float:left;}
</style>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=1.4"></script>
<title>baidu map demo</title>
</head>
<body>
<div id="allmap"></div>
</body>
</html>
<script type="text/javascript">
var map = new BMap.Map("allmap");            // 创建Map实例
var point = new BMap.Point(125.3249352, 43.8593245);    // 创建点坐标

var marker = new BMap.Marker (point);
marker.setTitle ("This is a Marker.");
map.addOverlay (marker);

map.centerAndZoom(point,15);                     // 初始化地图,设置中心点坐标和地图级别。
map.enableScrollWheelZoom();                            //启用滚轮放大缩小

map.addEventListener('click', function(e){
console.log(e.point);
});
</script>

-----代码1结束--------

对于坐标偏移,百度的官方解释是 国家要求 + 百度的二次加密。百度支持把其
他坐标 (google的, gps设备的)转换为百度地图的坐标,这样显示在百度地图上
的标记位置就不偏移了。

2. 解决

使用下述代码,在百度地图上,标记点的位置是正确的。

下述代码抄自网络并修改,最初来源已不可考。谢谢原作者。

代码2在代码1的基础上做了修改,调用了百度的API[http://api.map.baidu.com/api?v=1.4],及[convertor.js]即代码3。

用到了 javascript的闭包和回调。

------代码2开始------

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body, html,#allmap {width: 100%;height: 100%;overflow: hidden;margin:0;}
#l-map{height:100%;width:78%;float:left;border-right:2px solid #bcbcbc;}
#r-result{height:100%;width:20%;float:left;}
</style>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=1.4"></script>
<script type="text/javascript" src="convertor.js"></script>
<title>baidu map demo</title>
</head>
<body>
<div id="allmap"></div>
</body>
</html>

<script type="text/javascript">

var map = new BMap.Map("allmap");            // 创建Map实例

var point = new BMap.Point(125.3249352, 43.8593245);    // 创建点坐标
BMap.Convertor.translate(point, 0, function(point){

var marker = new BMap.Marker (point);
marker.setTitle ("This is a marker");
map.addOverlay (marker);

map.centerAndZoom(point,15);                     // 初始化地图,设置中心点坐标和地图级别。
map.enableScrollWheelZoom();                            //启用滚轮放大缩小

map.addEventListener('click', function(e){
console.log(e.point);})

});
;
</script>

------代码2结束------

------代码3开始------

//2011-7-25
(function(){        //闭包
function load_script(xyUrl, callback){
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = xyUrl;
//借鉴了jQuery的script跨域方法
script.onload = script.onreadystatechange = function(){
if((!this.readyState || this.readyState === "loaded" || this.readyState === "complete")){
callback && callback();
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
if ( head && script.parentNode ) {
head.removeChild( script );
}
}
};
// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
head.insertBefore( script, head.firstChild );
}
function translate(point,type,callback){
var callbackName = 'cbk_' + Math.round(Math.random() * 10000);    //随机函数名
var xyUrl = "http://api.map.baidu.com/ag/coord/convert?from="+ type + "&to=4&x=" + point.lng + "&y=" + point.lat + "&callback=BMap.Convertor." + callbackName;
//动态创建script标签
load_script(xyUrl);
BMap.Convertor[callbackName] = function(xyResult){
delete BMap.Convertor[callbackName];    //调用完需要删除改函数
var point = new BMap.Point(xyResult.x, xyResult.y);
callback && callback(point);
}
}

window.BMap = window.BMap || {};
BMap.Convertor = {};
BMap.Convertor.translate = translate;
})();

------代码3结束------

4. 完整代码在[http://download.csdn.net/detail/younggift/8334749]。

参考:
1. [http://www.cnblogs.com/funnydavid/archive/2011/03/02/1969244.html]

2. 百度的解释
[http://developer.baidu.com/map/question.htm]
百度坐标为何有偏移?

国际经纬度坐标标准为WGS-84,国内必须至少使用国测局制定的GCJ-02,对地理位置进行首次加密。百度坐标在此基础上,进行了BD-09二次加密措施,更加保护了个人隐私。百度对外接口的坐标系并不是GPS采集的真实经纬度,需要通过坐标转换接口进行转换。

如何从其他体系的坐标迁移到百度坐标?

开发者可以使用坐标转换接口进行转换。JavaScript API 、Android SDK、iOS SDK的开发用户可直接调用相应方法进行转换。

3. 百度的手册
[http://developer.baidu.com/map/changeposition.htm]

------------------------------------------------------------

博客会手工同步到以下地址:

[http://giftdotyoung.blogspot.com]

[http://blog.csdn.net/younggift]