S#公式是由各種操作數(常量、變數、或子公式)和操作符(算符、函數、屬性、方法、或子公式)組合而成,公式和子公式可以形成複雜嵌套結構。S#還在公式級別提供了相當於其他語言語句級別的系統專用公式,使得S#公式表達能力超強,易用性也好,可以說是最為炫酷的公式表達風格。 ...
ShoneSharp語言(S#)的設計和使用介紹
系列(8)— 最炫“公式”風
作者:Shone
聲明:原創文章歡迎轉載,但請註明出處,https://www.cnblogs.com/ShoneSharp。
摘要: S#公式是由各種操作數(常量、變數、或子公式)和操作符(算符、函數、屬性、方法、或子公式)組合而成,公式和子公式可以形成複雜嵌套結構。S#還在公式級別提供了相當於其他語言語句級別的系統專用公式,使得S#公式表達能力超強,易用性也好,可以說是最為炫酷的公式表達風格。
軟體: S#語言編輯解析運行器,運行環境.NET 4.0,綠色軟體,單個EXE直接運行,無副作用。網盤下載鏈接為https://pan.baidu.com/s/1dF4e08p
公式是編程語言最基礎的表達結構,所有語言都支持公式。各種語言的公式語法各不相同,表達能力也差別很大。比如LISP公式語法很簡單,就是S表達式,但表達能力卻非常強大,負面的影響的易用性不好。有的語言則語法比較好用,而表達能力有限。
S#語言的公式語法符號很多,表達能力非常強,易用性也不錯,其綜合能力在所有語言中也是領先的,可以說“S#帶來了最炫的公式表達風格”。
對於上述主觀論斷,看好本博文後有不服者可以來辯。另外語言是相通的,其他語言公式的特性基本上S#都支持,因此其他語言愛好者看一眼本文,也可以提升對自己所學語言的領悟。
一、公式概念
公式是由各種操作數(常量、變數、或子公式)和操作符(算符、函數、屬性、方法、或子公式)組合而成,可以解析並計算得到數據值。
由於公式的操作數和操作符都可能是另一個公式構成,因此一個公式可以包含非常複雜的嵌套結構,用來表達或計算各種更加複雜的數據。這一點和LISP語言非常相似。
S#公式中可以包含註釋,註釋分行註釋//xxx和塊註釋/*xxx*/,使用方法與C#相同。
二、常量引用公式
常量是系統預先定義命名的數據值,在任何上下文中都是保持數據值不變。不同數據類型預定義了不同的常量,具體可以在軟體的"所有"面板中查閱。
常量引用公式是直接引用常量名稱,是最簡單的公式之一,其計算結果就是系統預定義的數據值(如true、false、null等)。
三、變數引用公式
變數是用戶自定義命名的數據值,必須先定義後引用。在不同上下文中用戶可以定義同名變數,因此變數引用是依賴於上下文的。
變數的命名規則基本與C#相同,即字母或_開頭的包含一道多個字母或數字或_的名字,併排除與系統預定義常量以及其他關鍵字重覆的名字(包括para, eval, if, case, switch, each, eachx, for, forx, dup, dupx, iter, do, sum, prod, integal, load, parse, call, func, class, foreach, while, var, default, return, break, continue, in, else, true, false, null, local, parent, this, base, host, import, include等)。
變數引用公式是直接引用變數名稱,也比較簡單,但其計算結果需要在上下文中進行查找。上下文其實指的是變數堆棧鏈,S#的有些公式(下麵會介紹)帶有局部變數堆棧,允許定義變數,如果這些公式中又嵌套了其他帶有局部變數堆棧的子公式,那麼就會形成變數堆棧鏈。
變數引用預設會先在當前公式最近的局部變數堆棧查找,如果找不到就會到上一級的變數堆棧,一層層找直至找到為止(類似JavaScript)。看一下代碼實例就明白了:
{ a = 10 , b = { c = 20 , d = a + c //d計算結果為30 } }
四、運算符號公式
運算符號(簡稱算符)是系統預定義的一些操作符符號,用於對操作數進行計算求值。其功能類似與函數調用,只不過寫法上有指定的格式,如單目、雙目、三目以及其他。
通常算符都是針對特定數據類型的,但有些算符可以處理多種類型,為便於理解下麵從簡單類型到複雜類型分開介紹。
4.1 數值算符 + - * / % ^
數值算符用於對操作數進行數值計算,與其他語言不同,S#可以處理多種類型,另外操作數互換結果可能不同。舉例如下:
-10, -[10,20], -{10,20,30} //取負 10+3, [10,20]+3, {10,20,30}+3 //加法 10-3, [10,20]-3, {10,20,30}-3 //減法 10*3, [10,20]*3, {10,20,30}*3 //乘法 10/3, [10,20]/3, {10,20,30}/3 //除法 10%3, [10,20] %3, {10,20,30}%3 //取餘 10^3, [10,20]^3, {10,20,30}^3 //乘方
4.2 布爾算符 < > <= >= == != ! && ||
布爾算符用於對操作數進行計算獲得布爾值,基本用法與C#相同。舉例如下:
10<20 //判斷是否小於 10>20 //判斷是否大於 10<=20 //判斷是否小於等於 10>=20 //判斷是否大於等於 10==20 //判斷是否等於 10!=20 //判斷是否不等於 !true //判斷條件取反 true && false //並且判斷,即兩個條件是否都成立 true || false //或者判斷,即兩個條件是否有一個條件成立
4.3 數組算符 [,] , ~ [] . $ # & | .. .$ .~ .< .>
數組是包含同類型數據的一個集合,S#有專用於對數組進行構造和運算的算符。
[10, 20, 30, 40, 50] //構造數組的最基本用法 10, 20, 30, 40, 50 //構造數組的簡化用法,獨立沒有衝突時可以省略[] ~[10, 20, 30, 40, 50] //數組元素反向重排,結果[50,40,30,20,10] [10, 20, 30, 40, 50][2] //索引數組元素,索引號從0開始,可以變數,結果30 [10, 20, 30, 40, 50].2 //索引數組元素,索引號從0開始,必須數字,結果30 [10, 20, 30, 40, 50][2,3,4] //離散索引多個數組元素,索引號從0開始,可以變數,結果[30,40,50] [10, 20, 30, 40, 50][2:4] //連續索引多個數組元素,索引號從0開始,可以變數,結果[30,40,50] 3$10, 2$[20,30] //整體重覆數組元素,結果[10,10,10,20,30,20,30] 3#10, 2#[20,30] //交錯重覆數組元素,結果[10,10,10,20,20,30,30] [10,20,30]&[1,2] //整體插入數組元素,結果[10,1,2,20,1,2,30] [10,20,30]|[1,2] //交錯插入數組元素,結果[10,1,20,2,30] 10..15 //構造連續範圍數組,結果[10,11,12,13,14,15] 10..15.$2 //構造指定步長的連續範圍數組,結果[10,12,14] 10..15.~2 //構造接近步長的等距範圍數組,結果[10,11.666666666666666,13.333333333333332,14.999999999999998] 10..15.<2 //構造小於等於步長的等距範圍數組,結果[10,11.666666666666666,13.333333333333332,14.999999999999998] 10..15.>2 //構造大於等於步長的等距範圍數組,結果[10,12.5,15]
4.4 列表算符 {,} ; ~ [] . $$ ## $$$ ### & | ... .$ .~ .< .>
列表是可以包含不同類型數據的一個集合,與數組類似,S#有專用於對列表進行構造和運算的算符。
列表與數組的使用原則:單層同類型儘量用數組,性能更好;多層次數據結構使用列表,表達能力更強。
{true, 20, 30, 40, 'hjx'} //構造列表的最基本用法 true; 20; 30; 40; 'hjx' //構造列表的簡化用法,獨立沒有衝突時可以省略{} ~{10, 20, 30, 40, 50} //數組元素反向重排,結果{50,40,30,20,10} {true, 20, 30, 40, 'hjx'} [2] //索引列表元素,索引號從0開始,可以變數,結果30 {true, 20, 30, 40, 'hjx'}.2 //索引列表元素,索引號從0開始,必須數字,結果30 {true, 20, 30, 40, 'hjx'} [2,3,4] //離散索引多個列表元素,索引號從0開始,可以變數,結果{30,40,'hjx'} {true, 20, 30, 40, 'hjx'} [2:4] //連續索引多個列表元素,索引號從0開始,可以變數,結果{30,40,'hjx'} 2$${true, 10, 'hjx'} //整體重覆列表元素,結果{true,10,'hjx', true,10,'hjx'} 2##{true, 10, 'hjx'} //交錯重覆列表元素,結果{True,True,10,10,'hjx','hjx'} 2$$${true, 10, 'hjx'} //整體多重列表元素,結果{{true,10,'hjx'}, {true,10,'hjx'}} 2###{true, 10, 'hjx'} //交錯多重列表元素,結果{{True,True},{10,10},{'hjx','hjx'}} {true, 10, 'hjx'}&{1,2} //整體插入列表元素,結果{True,1,2,10,1,2,'hjx'} {true, 10, 'hjx'}|{1,2} //交錯插入列表元素,結果{True,1,10,2,'hjx'} 10...15 //構造連續範圍列表,結果{10,11,12,13,14,15} 10...15.$2 //構造指定步長的連續範圍列表,結果{10,12,14} 10...15.~2 //構造接近步長的等距範圍列表,結果{10,11.666666666666666,13.333333333333332,14.999999999999998} 10...15.<2 //構造小於等於步長的等距範圍列表,結果{10,11.666666666666666,13.333333333333332,14.999999999999998} 10...15.>2 //構造大於等於步長的等距範圍列表,結果{10,12.5,15}
4.5 數據表算符
數據表是包含一系列鍵值數據對的集合,註意與其他語言不同,數據錶帶有局部變數堆棧,其鍵就是堆棧中的變數名稱,而值就是變數值,而且數據表的基類是數據表,這就意味著數據表也可以通過索引進行訪問。
數據表可以很複雜,其中變數的作用範圍可以有多種,後面將專題介紹。
{A=5, B=[1,2], C={10,20,30}} //構造基本數據表 {A=5, B=[1,2], C={10,20,30}}['B'] //通過鍵值索引數據表元素,結果[1,2] {A=5, B=[1,2], C={10,20,30}}.B //通過屬性訪問數據表元素,結果[1,2] {A=5, B=[1,2], C={10,20,30}}[1] //通過列表索引數據表元素,結果[1,2] {A=5, B=[1,2], C={10,20,30}}.1 //通過列表索引數據表元素,結果[1,2]
4.6 特殊算符 () <> ?? ? $ & *
(10+20) //用於對公式進行隔離計算,可以改變其優先順序,結果15 (10, 20) //構造二維點坐標 (10, 20, 30) //構造三維點坐標 <10, 20> //構造二維向量 <10, 20, 30> //構造三維向量 null??10 //非空值計算符號,左側值為空則取右側值,結果10 ?’(20+30)/2’ //對字元串解析並求值,結果25 $’c:\hjx.shone’ //打開字元串表示的文件,並對其內容解析並求值 &xxx //獲取右側公式解析樹節點的標記引用,類似C#中指針取地址 *xxx //獲取右側公式解析樹節點的結果值引用,類似C#中指針取值
五、面向對象公式
5.1 屬性調用
屬性調用是面向對象的表達方式之一,可以方便表達被調用對象直接暴露的相關信息,其基本格式是:對象.屬性名稱。
[10,20,30,40,50].Count //獲取數組的個數,結果5
註意屬性調用優先查找被調用對象的局部變數堆棧,如果沒有變數堆棧或找不到,就會去調用該對象類型的系統預定義屬性(不同數據類型預定義了不同的方法,具體可以在軟體"所有"面板中查閱),如果還沒有則報錯。例如:
{A=1,B=2}.Count //結果2 {A=1,B=2,Count=100}.Count //結果100,註意優先調用Count變數而不是列表的屬性Count。
理論上屬性寫法可以用函數代替(如count(xxx)),但屬性寫法更加簡潔直觀,可以形成很有特色的鏈式寫法,如A.B.C….,一直點下去。
5.2 方法調用
方法也是面向對象的表達方式之一,可以方便表達針對被調用對象的各種操作,其基本格式是:對象.方法名稱(參數,…)。
[10,20,30,40,50].Sub(2) //從數組[10,20,30,40,50]的索引2位置開始提取子數組,結果[30,40,50]
註意方法調用也會優先查找被調用對象的局部變數堆棧,如果沒有變數堆棧或找不到,就會去調用該對象類型的系統預定義方法(不同數據類型預定義了不同的方法,具體可以在軟體"所有"面板中查閱),如果還沒有則報錯。例如:
{A=1,B=2}.Sub(1) //結果{2} {A=1,B=2,Sub(x)=10*x}.Sub(1) //結果10,註意優先調用Sub函數變數而不是列表的方法Sub
理論上方法寫法也可以用函數代替(如Sub(xxx,2)),但方法比較直觀,可以形成很有特色的鏈式寫法,如A.B().C()….,一直點下去。
六、面向函數公式
6.1 函數調用
函數調用是S#公式使用最為廣泛的表達方式,其基本格式是:函數名稱(參數,…)。其中每個參數又可以是一個子公式,從而可以形成更加複雜的公式嵌套結構。
最常用的函數是數值函數,註意與其他語言不同,S#數值函數大都支持多種數據類型。例如求餘弦函數值:
cos( 30 ) //結果0.86602540378443871 cos( [ 10 , 20 , 30 ] ) //結果[0.984807753012208,0.93969262078590843,0.86602540378443871] cos( { 10 , [ 20 , 30 ] , 40 } ) //結果{0.984807753012208,[0.93969262078590843,0.86602540378443871],0.766044443118978}
註意函數調用也會優先查找當前最近的局部變數堆棧,如果找不到就會到上一級的變數堆棧,一層層往上找,如果還找不到,就會去調用系統預定義函數(不同數據類型預定義了不同的函數,具體可以在軟體"所有"面板中查閱),如果還沒有則報錯。例如:
{a=1, b=cos(30)} //結果{a=1,b=0.86602540378443871} {cos=x=>10*x, b=cos(30)} //結果{cos=x=>10*x,b=300},註意優先調用cos函數變數而不是求餘弦函數值。
6.2 函數定義
函數式語言強調的“函數是一等公民”,指的是函數自身也可以作為一個變數,即用戶可自定義命名的函數變數,也支持先定義後引用,在不同上下文中可以定義同名函數變數。
上面的x=>10*x其實就是一種匿名的函數定義(與C#類似),由於函數定義有多種形式和高級使用特性,下麵一個章節“一等公民函數愛炫巧”會專門講函數定義。
六、系統專用公式
前面講的都是其他語言大都有公式表達結構,本節列出的很多是S#特有的表達方式,語法上類似函數調用,但是使用專用關鍵字和分隔符。這裡很多公式其實都等價於其他語言的語句功能了。
6.1 直接求值公式
parse/include/load/call(參數)
parse('(20+'+'30)/2') //?算符的增強版,可對變數公式進行字元串解析並求值,結果25 include('c:\hjx. shone') //$算符的增強版,可打開字元串文件對其內容解析,同時整個解析樹都載入進來 load('c:\hjx. '+'shone') //$算符的增強版,對變數公式進行字元串解析並打開文件,再對其內容解析並求值 call('c'+'os', 30) //可以通過字元串變數公式,動態調用變數或函數,結果0.5
6.2 順序求值公式
eval(局部變數堆棧: 結果公式)
順序求值公式通過建立局部變數堆棧並對結果公式進行求值和輸出。其中局部變數堆棧有一到多個變數賦值構成,用逗號分割。變數賦值寫法是:變數名稱=變數公式。結果公式可以直接引用局部變數,若輸出數組或列表可以採用省略寫法。例如:
eval(a=1, b=2: a+b) //結果3 eval(a=1, b=2: a,3$b,a) //結果[1,2,2,2,1] eval(a=1, b=2: a;3$b;a) //結果{1,[2,2,2],1}
順序求值公式的使用非常廣泛,能力等價於C#中的順序求值語句{;;return;}。
6.3 條件求值公式
if(條件公式? 結果公式1 : 結果公式2)
條件求值公式先計算條件公式併進行判斷,如果為真則對結果公式1進行求值和輸出,否則對結果公式2進行求值和輸出。條件公式沒有變數堆棧,結果公式若輸出數組或列表也可以採用省略寫法。例如:
if(10>5? 1: 2) //結果1 if(10>5? 1,2: 2) //結果[1,2] if(10>5? 1;2: 2) //結果{1,2}
條件求值公式的使用也非常廣泛,能力等價於C#中的條件求值公式(?:)或語句(if else)。
6.4 分支求值公式
case(數據公式? 分支公式系列 : 預設結果公式)
分支求值公式先計算數據公式,然後對分支公式系列的每個分支進行測試,如果等於某個分支公式值,則輸出該分支結果公式的計算結果,否則輸出預設結果。每個分支的寫法是:分支公式->結果公式,多個分支間用逗號分隔。分支公式沒有變數堆棧,結果公式若輸出數組或列表也可以採用省略寫法。例如:
case(1+2? 1->5, 3->10: 0) //結果10
分支求值公式能力等價於C#中的分支求值語句(switch)。
6.5 判斷求值公式
switch(單個變數賦值? 分支公式系列 : 預設結果公式)
判斷求值公式先計算變數公式並賦值,然後對分支公式系列的每個分支進行測試,如果某個分支公式值為真,則輸出該分支結果公式的計算結果,否則輸出預設結果。與case差別是,switch有變數堆棧,分支公式必須是布爾值且最好引用前面賦值的變數,結果公式若輸出數組或列表也可以採用省略寫法。例如:
switch(x=1+2? x<1->5, x>2->10: 0) //結果10
判斷求值公式能力等價於其他語言的模式匹配求值語句。
6.6迴圈求值公式
each/eachx(迴圈變數配對序列 : 結果元素公式)
each單重迴圈求值公式首先建立迴圈變數堆棧,並把迴圈變數配對的數據值(通常是數組或列表)逐個迴圈賦值給變數並對結果元素公式進行求值,最終合併輸出為數組或列表。其中迴圈變數配對寫法是:變數名稱@數據公式。有多個迴圈變數配對時用逗號分割,註意每組迴圈次數以第一個變數為準。迴圈求值公式有局部變數堆棧,結果公式可以直接引用迴圈變數,若輸出數組或列表可以採用省略寫法。另外each表示輸出數組,eachx則輸出列表。例如:
each(x@[1,2,3]: 2*x) //結果[2,4,6] each([email protected]: 2*x) //結果[2,4,6,8,10] each([email protected]: x,2*x) //結果[1,2,2,4,3,6,4,8,5,10] each([email protected], [email protected]: (2*x,y)) //多迴圈結果[(2,5),(4,6),(6,7),(8,8),(10,9)] eachx(x@[1,2,3]: 2*x) //結果{2,4,6} eachx([email protected]: 2*x) //結果{2,4,6,8,10} eachx([email protected]: x,2*x) //結果{[1,2],[2,4],[3,6],[4,8],[5,10]} eachx([email protected]: x;2*x) //結果{{1,2},{2,4},{3,6},{4,8},{5,10}} eachx([email protected], [email protected]: (2*x,y)) //多迴圈結果{(2,5),(4,6),(6,7),(8,8),(10,9)}
each迴圈求值公式能力等價於C#語言的迴圈語句(foreach),甚至更強。
each/eachx(迴圈變數配對序列; 過濾條件公式 : 結果元素公式)
each單重迴圈求值公式中如果中間加入過濾條件,那麼只輸出符合過濾條件結果。例如:
each([email protected];k%2==0: k) //結果[2,4]
each/eachx(索引變數: 迴圈變數配對序列 : 結果元素公式)
each單重迴圈求值公式中如果前面加入索引變數,那麼在每次迴圈時會自動為索引變數賦值,從0開始,每個迴圈自動加1。例如:
each(i: [email protected]: k*10+i) //結果[10,21,32,43,54]
dup/dupx(迴圈變數配對序列 : 結果元素公式)
dup多重迴圈求值公式語法與each類似,區別是多組迴圈時會輸出多重迴圈的結果,數據量更多。例如:
dup([email protected], [email protected]: (2*x,y)) //多重迴圈結果[(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),(2,14),(2,15),(2,16),(2,17),(2,18),(2,19),(2,20),…] dupx([email protected], [email protected]: (2*x,y)) //多重迴圈結果{{(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),(2,14),(2,15),(2,16),(2,17),(2,18),(2,19),(2,20)},… }
for/forx(迴圈變數堆棧; 迴圈條件公式; 變數賦值序列 : 結果元素公式)
for迴圈求值公式首先建立迴圈變數堆棧,並執行迴圈直到不滿足條件公式,每次有效迴圈先對結果元素公式求值再執行迴圈賦值序列,最終合併輸出為數組或列表。其中變數賦值的寫法是:變數名稱=賦值公式,有多個變數賦值時用逗號分割。for迴圈求值公式有局部變數堆棧,結果公式可以直接引用迴圈變數,若輸出數組或列表可以採用省略寫法。另外for表示輸出數組,forx則輸出列表。例如:
for(i=0; i<5; i++: i*2) //結果[0,2,4,6,8] for(i=0,j=2; i<5; i++,j+=2: (i*2,j)) //多個變數結果[(0,2),(2,4),(4,6),(6,8),(8,10)] for(i=0; i<5; i++: for(j=2; j<5; j+=2: (i*2,j))) //多重迴圈結果[(0,2),(0,4),(2,2),(2,4),(4,2),(4,4),(6,2),(6,4),(8,2),(8,4)]
for迴圈求值公式能力等價於C#語言的迴圈語句(for)。
6.7 迭代求值公式
iter (結果變數賦值; 迴圈變數配對序列 : 結果賦值公式)
iter迭代求值公式首先建立迴圈變數堆棧,初始化結果變數賦值,並把迴圈變數配對的數據值(通常是數組或列表)逐個迴圈賦值給迴圈變數並對結果賦值公式進行求值,最終輸出結果變數的最終值。迭代求值公式有局部變數堆棧,結果賦值公式必須引用結果變數。例如:
iter(s=0; [email protected]: s+=2*k) //結果30
iter (結果變數賦值; 索引變數: 迴圈變數配對序列 : 結果賦值公式)
iter迭代求值公式中如果中間加入索引變數,那麼在每次迴圈時會自動為索引變數賦值,從0開始,每個迴圈自動加1。
iter(s=0; i: [email protected]: s+=i) //結果10
do(結果變數賦值; 迴圈變數堆棧; 迴圈條件公式; 變數賦值序列 : 結果賦值公式)
do迭代求值公式首先建立迴圈變數堆棧,並執行迴圈直到不滿足條件公式,每次有效迴圈先對結果賦值公式求值再執行迴圈賦值序列,最終輸出結果變數的最終值。do迭代求值公式有局部變數堆棧,結果賦值公式必須直接引用結果變數。例如:
do(s=0; i=0; i<5; i++: s+=i) //結果10
6.8 區間累計公式
區間累計公式其實可以使用迭代公式替換,只是寫法複雜一些。考慮到他們在數值計算中經常使用,因此設置專用公式可以提高表達能力。
sum(自變數=開始公式,結束公式; 求和公式)
sum區間累計求和公式首先建立自變數堆棧,並按區間從開始到結束步長為1,逐個迴圈賦值給自變數並對求和公式進行求值,最終合併輸出累加結果。sum公式有局部變數堆棧,結果元素公式可以引用自變數。例如:
sum(x=1,5: x) //結果15
prod(自變數=開始公式,結束公式; 求積公式)
prod區間累計求積公式與sum求和類似,區別是對求積公式結果進行乘法累積。例如:
prod(x=1,5: x) //結果120
integal(自變數=開始公式,結束公式; 積分公式)
prod區間累計積分公式與sum求和類似,區別是結果是針對區間的定積分。註意積分公式不用包含dx。例如:
integal(x=0,5: 1) //結果5 integal(x=0,5: x) //結果12.5 integal(x=0,5: x*x) //結果41.6666716337204
已有電腦專家論證過,編程語言只要具備順序、條件、迴圈三大控制語句,其演算法表達能力是等價的。S#在公式級別就提供了相當於其他語言語句級別的演算法能力,更不用說S#還有語句級別的表達。
看了本文,您是否同意“S#是最炫酷的公式表達”?!
聲明:原創文章歡迎轉載,但請註明出處,https://www.cnblogs.com/ShoneSharp。
軟體: S#語言編輯解析運行器,運行環境.NET 4.0,綠色軟體,單個EXE直接運行,cen無副作用。網盤下載鏈接為https://pan.baidu.com/s/1dF4e08p