如果因為過早學一樣東西而有損後來的學習成長,那這種學習無異於“揠苗助長”。正是基於這個原因,教會小學生理解x=x+1是有害無益的。 ...
對程式員而言,類似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 +: #<void> 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 #<procedure 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無限開方的問題,傳送門。