对程序员而言,类似x=x+1的代码是再常见不过的了,几乎所有常见的编程语言教程在开始初级教程的时候,都会拿这个问题的计算来做示例,比如对于C#,会像下面这样的代码:
int x=0;
x=x+1;
也可以这样写:
x+=1;
也可以像C语言那样,这样写更简单:
int x=0;
x++;
++x;
其它编程语言都大同小异了,程序员朋友们都知道上面的代码无非就是将变量x的值增加了1,或者说为变量x赋了一个比它原来大1的新数值。但是,如果你打算把这行代码告诉一个小学生,甚至一个初中生,以此想说明coding是多么简单就错了,如果教他学编程,那就是大错,为什么要这样说呢?
为了这个问题,我将x=x+1这个式子写给几个小学生看,他们的第一反应是“这个等式怎么这样写啊?”,“这个式子写的不对,两边不相等!”。我说这不是等式,这表示将变量x的值变大一个数,也就是将变量x的值加1后再赋值给变量x...后面的话没法继续进行下去了,小学生的神情是这样的:
幸好我是在跟中国小朋友交谈,如果我给几个美国小朋友说x=x+1,说不定会有家长控告我“损害了小孩正常的思维逻辑”。(PS:1968年,美国一位母亲投诉老师教会了她孩子字母“O”,状告幼儿园破坏了孩子的想象力,结果幼儿园败诉。)于是我顺着小学生的思路,将这个式子修改成下面这样:
y = x + 1
我问这样写你们可以明白吗?然后他们说这看起来好像是一个方程,但怎么有2个未知数?我说这的确可以看作是一个方程,到了初中阶段,老师会教你们这是“2元1次方程”,上面的等式等同于:
x + 1 -y =0
这时候有小朋友说我知道了,这叫做“等式变换”。
我说没错,但是写成y= x + 1 这样能更好的表示未知数 x 和 y的关系,在这里这个方程的意思表示未知数y总是比x要大一个数....每当x有一个确定的值,就能得到一个确定的y值,这样x和y就建立了一种关系,我们称这种关系为“函数关系”,假设这个函数为f,那么这个函数关系可以记为:
y=f(x)
上面的式子表示 y是x的函数,x是自变量,也可以说x是函数f的参数。这个函数的概念将是你们在初中学的内容,比如以后要学的计算三角形问题的三角函数。
讲到这里,小学生们表示不是很理解了,本来是一个方程,为什么要提出函数的概念呢?
于是,我继续讲,请看下面两个“方程”有什么区别?
y = x +1
b = a +1
有同学说这两个式子看起来差不多啊,只不过表示未知数的英文字母不同。
我说是的,在上面的方程中,等式右边未知数不管用字母x表示,还是用a表示,它们都只是一个符号而已,同样等式左边的字母y或者b也都仅仅是一个符号而已,所以上面两个方程对应的函数可以像下面这样表示:
y= x + 1 => y=f1(x)=x+1
b= a + 1 => b=f2(a)=a+1
所以不管自变量用哪个英文字母表示,自变量x或者自变量a 都是函数的形式参数,函数计算的结果都是将这个参数值加1后的结果,那么上面的函数f1或者函数f2本质上都是相同的,也就是函数的名字不是最重要的,重要的是函数的内容,也就是函数如何处理参数,计算结果的过程。当我们不晓得函数应该取什么名字的时候,我们就叫这个函数为“匿名函数”吧。
这个时候有同学又问了:既然函数名都不重要了,那对于y=f(x)这个函数关系式,一定需要y吗?
这个问题真是棘手,我想了想,说可以不需要y,因为y只是一个符号,用它来绑定这个函数的计算结果而已,比如对比下面这两个函数关系式:
y=f(x)
z=f(x)
此时我很想对小学生们说:“同学,这里我们可以把上面两个式子中的y,z都看作一个变量...”,但一想到他们很可能会马上问什么是变量、变量跟自变量有啥区别等等新问题就打住了,更不能说可以用变量y来存储函数f的计算结果,因为在整个中学数学中,就没有“变量是用来存储计算结果”这个说法,而是把变量当作是显式数字一样,对其进行代数计算的,代数式中有自变量和因变量,现在教小学生程序变量的概念,很可能会误导他们的数学逻辑思维。如果因为过早学一样东西而有损后来的学习成长,那这种学习无异于“揠苗助长”,这也是我为何一直不愿轻易教授少儿编程的原因。正是基于这个原因,教会小学生理解x=x+1是有害无益的。
上面说不能轻易的教授少儿编程,但不是说一定不能进行少儿编程教学,而是要注意少儿编程语言的选择,以及教授的方式方法,学习少儿编程的目的是更加有利于学生在学校学校的知识的理解应用,而不是为了编程而学编程,本末倒置。基于这个特点,选择函数式编程语言而不是命令式语言,就是少儿编程的不二选择了。Lisp是最古老的编程语言,也是第一个函数式语言,善于处理符号计算问题,Scheme语言是Lisp家族最简单的方言,特别适合在校学生学校学习编程,理解计算机编程的原理,是MIT的SCIP(Structure and Interpretation of Computer Programs)课程使用的语言。
下面,使用Scheme编程语言来帮助学生理解 y=x+1 的问题。
Chez Scheme for Windows. make by bluedoctor. 2019.11.18
Chez Scheme Version 9.5.3
Copyright 1984-2019 Cisco Systems, Inc.
> (define y (+ x 1))
Exception: variable x is not bound
Type (debug) to enter the debugger.
>
在Scheme中,使用define操作,定义一个变量并且可以将它绑定到一个对象上。上面的代码
(define y (+ x 1))
本意是想表示本文前面说的等式:
y= x+1
但Scheme的REPL(交互式环境)提示说变量x没有绑定。注意这里说变量x没有绑定,而不是说它没有定义,实际上变量x是没有定义的,修改为下面的程序:
> (define x)
> (define y 1)
> (define y (+ x 1))
Exception in +: # is not a number
Type (debug) to enter the debugger.
上面定义了变量x,但没有绑定它的初始值,所以认为它是一个没有任何返回值的过程,而加法操作需要一个数字参数。重新定义变量x并给它绑定一个数值:
> (define x 2)
> (define y (+ x 1))
> y
3
然而上面的变量y并不是一个函数,它只是一个变量,它的值在定义的时候就立刻求值了,之和跟变量x不再有关系。所以上面这段程序仍然无法表达y=x+1的含义。
前面我们说了函数最重要的是函数的定义,而不是它的名字,所以下面我们直接定义一个计算返回参数x的值加1的结果的匿名函数:
> ((lambda (x) (+ x 1))
2)
3
lambda是Scheme中定义函数的操作,它的第一个“参数”是函数的参数,第二个“参数”是函数体(函数操作内容)部分。定义好当前函数后立刻使用一个参数来调用即可计算出函数的值。
我们可以把这个匿名函数绑定给变量y,这样后续的函数操作就方便了:
> (define y (lambda (x) (+ x 1)))
> y
#
> (y 2)
3
> (define (y x) (+ x 1))
> (y 3)
4
>
从上面的代码可以看到我们有2种定义函数的方式,效果都是一样的,本质上第二种方式只不过是第一种方式的语法糖。第二种方式:
(define (y x) (+ x 1))
看起来就是最接近本文的数学方程的程序语言的函数定义了:
y= x + 1
到这里,我们不仅仅教会了小学生什么是函数,也顺便用数学中的函数概念,实现了Scheme编程语言的学习,之后就能使用Scheme语言来增强数学的学习了,例如B站有个小学生求解根号6无限开方的问题,传送门。