Powerful Pattern Matching in Flowpython
其實Pattern Matching是幾天前就搞定了的,為什麼放到現在來說,有兩個原因。
第一,這幾天在弄預編譯發行版,上傳到pypi。現在你已經可以簡單地獲得flowpython了(暫時只支持64位的Linux和32/64位的Windows 系統上的Python 3.6.x版本。
pip install flowpythonnpython -m flowpython -m enable # 使用flowpython語法n
第二,我想把這篇文章寫好。
不僅是符合flowpython面向優雅與可讀性編程的理念,模式匹配的引入,還可以極大地提升代碼的表現力。
當你用短小精悍且可讀性高的代碼表達出複雜的處理邏輯時,可能會覺得打開了新世界的大門。而這正是我想提供給所有python使用者的東西。
當然僅僅是模式匹配並不足說明flowpython的特質,在之後放出的示例代碼里,我會稍微引入一些屬於flowpython的語法,主要是為了展示可讀性。
我們知道在很多很多的函數式語言里,都有模式匹配存在,不過一般來說是更加註重對於類型的匹配。一個強大的類型系統的確可以幫助模式匹配高效的進行,我承認這是CPython的弱點。但是Python可以依靠靈活來展現出完全不同的東西。
我們拿scala來比較。
str match{n case "one" => 1n case "two" => 2n}n
這個是最簡單的了,和switch差不多的功能,在flowpython里我們這樣寫
condic str:n case "one" => 1n case "two" => 2n
但是只有switch算什麼模式匹配??
然後是類型匹配
val tup = ("123",123)ntup.asInstanceOf[(Any,Any)] match{n case (a:Int,b:Int) => a + bn case (a:String, b:Int) => Integer.parseInt(a) + bn}n
好,我們來看看flowpython
tup = ("123",123)ntomap = .f -> .var:Iterator -> map(f,var) -> tuple( _ ) ncondic +(tomap(type)) tup:n case (a,b):(int,int) => a + bn case (a,b):(str,int) => int(a) + bn
好吧,看起來flowpython挺長的,但我認為這裡其實是一個優勢,為什麼這麼說呢?
你可能已經發現了,flowpython的模式是自己定義的。
首先解釋一下 -> 的作用,我稱它為Arrow Transform,與flowpython支持的lambda定義長得很像,這其實是我試圖從內在unify語法的結果。Arrow Transform在flowpython里可以理解為lambda的嚴格形式,lambda則是它的惰性形式。這個語法就是,箭頭右邊的 _ 指代左邊的表達式,這使得我們可以從此拒絕無窮無盡的括弧,也可以模擬Kotlin裡面說的擴展函數(實際上因為Arrow Transform的存在,我們完全不再需要類的成員函數)。
在關鍵字 condic 後有一個 +( ) , 這是一個捕捉變數情形下的(Callable Object)模式,它表示捕捉匹配變數且進行條件的判斷。這裡可以記住,如果模式是由( )引導的,那麼將會有一個函數調用,函數調用在Python里總會加一個()不是么?
tomap 對象是一個柯里化的函數,它接受的第一個參數是一個函數參數,在接受第二個參數是一個Iterator對象時,把函數參數應用於該對象的每個元素並以tuple形式返回。
當case (a,b) 捕捉tup變數成功後,還要判斷(a,b)在預定義的模式作用下,和 : 後面的值是否相等。
我認為這個還是挺好玩的,但其實它還可以變得更強大,不過,我們慢慢講。我們先看看下面這個模式,然後一起來寫一些有趣的模式匹配。
test = 1ncondic +[is not] test:n case a:1 => print(f"{a} is not 1")n case a:2 => print(f"{a} is not 2")n
有沒有發現什麼?由 [ ] 引導的模式,叫做比較操作模式,我們可以在裡面填上任意一個比較操作符。來看一個例子。
test = 2ncondic[>=] test:n [>]n case 5 => print(f"{test} > 5")n [is not]n case 2 => print(f"{test} is not 2")nn case 2 => print(f"{test} >= 2")n otherwise => print("otherwise是不可能的,這輩子都不可能")n
你看,condic後寫的是默認模式,在第一個和第二個case里沒有使用,它們是針對單個case的特殊模式。
我建議把特殊模式寫在case的上一行,雖然我處於某種考量並沒有限制你把它們寫在同一行的自由,但那樣寫一般而言實在有點醜陋。
當然,scala可以隨意的使用模式匹配分解變數,還有case class這個神器。
case class soup(a:Int, b:(Int,String), c:List[Int])n// val a = 1 ; val b = (2, "123") ; val c = List(1,2,3) nsoup(a,b,c) match{n case (_,(2,_),list) => list.sumn case (1,tup, _) => tup._1n} n
你看,這種奇形怪狀的匹配都能做,真的是強無敵。
那flowpython呢?case class不存在,但是可以做一些別的模擬。
caseclass = .*args, **kwargs -> ret where:n ret = args -> _ if not kwargs else _ +( kwargs,)n# a = 1; b = (2, "123") ; c = List(1,2,3)ncondic +[==] caseclass(a,b,c):n case (_, (B ,_), lis)->B:2 => list -> sum(_)n case (A, tup, _ )->A:1 => tup[0]n
感覺怎麼樣?
注意到,在case 子句的後面,出現的第一個箭頭不會被認為是Arrow Transform,而是一種新的語法,叫做 Matching Filter ,它允許我們在捕獲變數後只對局部結果進行判斷。
實際上我認為flowpython這樣甚至更靈活,但是其實缺點也很明顯:沒有強大的類型系統,所有檢測都是動態的,換句話說,比較低效的(但並不總是如此,尤其當你習慣動態語言的思維後)。
但是,你本不應該把Python當靜態語言寫,像那種希望完全把flowpython當成Scala寫的人,你也許是可以寫出很好看的代碼,但是我認為這樣可能是走入歧途(當然,孰是孰非我也不確定,畢竟有一種說法就是,使用Python已經是放棄了程序的效率。
最後,flowpython的模式匹配還有一種模式 { } ,我提到的很少,因為我在考慮要不要刪除。
condic{.x,y->x+y == 5} 1:n case 3 => print("1+3 == 5")n case 4 => print("1+4 == 5")n
這個模式叫做 Dual Callable Comparing Matching, 模式里一定要接受一個callable的接受兩個參數的對象。
這個模式現在並不支持使用變數捕獲 +{ }。我正在糾結,要不要給它添加這個功能,或者直接刪除它。
如果你對於flowpython有建設性的意見,請在github上提issue或者私下聯繫我 twshere@outlook.com 。
相關鏈接:
thautwarm/flowpython
thautwarm/flowpython
flowpython 0.1.1
推薦閱讀:
※python爬蟲之豆瓣音樂top250
※你用 Python 做過什麼有趣的數據挖掘/分析項目?
※Python 中用列表中的字元串元素作為名字來創建新變數
※50?python爬?代碼, 帶你正確打開知乎新世界!