中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

編程語言選型時,你需要考慮的幾個方面

2018-08-19    來源:raincent

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用


前言


畢業(yè)之后一直混跡在創(chuàng)業(yè)公司,經(jīng)歷過很多次產(chǎn)品從0到1的過程。產(chǎn)品在開發(fā)之前都會面臨一個常見問題,即技術(shù)選型,首當(dāng)其沖的是采用哪門編程語言來開發(fā)我們的產(chǎn)品。我的第一家公司是UCloud,三位老板都來自于騰訊,所以第一批員工不少都是騰訊兵,騰訊的主要開發(fā)語言是C++,有很多現(xiàn)成的輪子可以用,自然UCloud的產(chǎn)品都是用C++開發(fā)的,前端則采用的是當(dāng)時流行的PHP。

2014年Node大火,nodejs的非阻塞IO在非計算密集的高并發(fā)業(yè)務(wù)場景效果出眾,開發(fā)起來也會比較快;nodejs讓前端程序員也能參與到后端開發(fā)中來,在人力分配上面自由度會高一些;運行時有g(shù)c, 心智負擔(dān)比C++小。UCloud也順勢采用了node, 先是前端部門用它重寫了自己的產(chǎn)品邏輯,之后后端也陸續(xù)采用,包括我自己也用node重寫了整個uhost產(chǎn)品的后端。

2016年golang熱度很高,當(dāng)時正好有個新產(chǎn)品要基于docker來開發(fā),我們就開始嘗試用golang來寫后端服務(wù), golang雖然類型特性不多,也不支持函數(shù)式編程,但是其極佳的工程實踐和強勁的并發(fā)性能還是深得大家喜愛,F(xiàn)在這家公司,因為規(guī)模尚小,考慮到招人的難度就采用了主流的Java, 用golang來做ssh client。在做編程語言選型的時候,除了技術(shù)本身還有很多其他因素,這些因素要針對具體場景來分析,所以本文從通用性的角度考慮,只講技術(shù)層面的內(nèi)容。
 

看完這篇文章能收獲什么?


  • 對于開發(fā)人員,可以系統(tǒng)地了解各種類型特性,語法特性和編程范式
  • 對于架構(gòu)師或者技術(shù)leader,可以為編程語言的選型提供理論依據(jù)

類型系統(tǒng)


什么是類型?在軟件執(zhí)行的過程中,變量可以為很多值,定義變量的邊界的描述即類型。變量可以被賦予類型(即變量有邊界)的語言稱為類型語言(typed language),無類型語言(untyped language)沒有類型,或者說只有一個全局類型,能夠存儲所有的值。 類型語言我們見得多了,無類型的呢?lambda演算(pure λ-calculus)是無類型的,匯編和LISP也是無類型的.

變量類型的指定可以是顯式的

 
 
// golang 
var foo int

也可以是隱式的

 
-- haskell 
fac :: Int -> Int -- 這一行可以省略 
fac 0 = 1 
fac n = n * fac (n - 1)
 
fac 0 = 1 
fac n = n * fac (n - 1)

類型系統(tǒng)會自動賦予變量類型,這種行為稱為類型推斷(type inference)。
 

類型檢查


類型系統(tǒng)是類型語言的首要組成部分。類型系統(tǒng)的職責(zé)之一是跟蹤變量的類型,判斷代碼是否滿足類型約束,這種行為稱為類型檢查(typechecking), 類型檢查是保證程序穩(wěn)定運行的手段,同時又分為運行時檢查(runtime checks)和靜態(tài)檢查(static checks), 運行時檢查也叫動態(tài)檢查(dynamic checking).

類型系統(tǒng)做了靜態(tài)檢查,還有必要做動態(tài)檢查嘛?有,比如數(shù)組的邊界檢查,就必須在運行時做。運行時的類型檢查會導(dǎo)致程序運行終止(fail-stop),那為什么還要檢查呢?讓它運行到無法繼續(xù)執(zhí)行為止不就好了?類型檢查雖然會出錯,但是阻止了更惡劣的錯誤(untrapped errors)的發(fā)生,比如保證gc等機制能夠正常運轉(zhuǎn),讓程序能夠更平滑地退出。動態(tài)檢查的缺點是會導(dǎo)致fail-stop,也會消耗資源,影響性能,所以通常我們認為擁有靜態(tài)檢查的類型系統(tǒng)的語言會更穩(wěn)定高效。但是靜態(tài)檢查就足夠安全了嗎?不一定,因為某些語言在靜態(tài)檢查時沒有檢查一些危險操作,比如C語言中指針的運算和轉(zhuǎn)換,這類語言稱為weekly checked, 反之, 程序在編譯期間能夠盡可能發(fā)現(xiàn)所有的類型錯誤, 稱為strongly checked.

那么延伸一下,怎么區(qū)分一門語言是weekly checked, 還是strongly checked? 以下幾點可以作為判斷的依據(jù)。
 

Implicit type conversions


可以進行隱式類型轉(zhuǎn)換的語言屬于weekly checked, 如c++

 
int a = 3; 
double b = 4.5; 
a + b; // a將會被自動轉(zhuǎn)換為double類型,轉(zhuǎn)換的結(jié)果和b進行加法操作

Pointers


允許指針運算的語言屬于weekly checked, 比如c

 
 int num [] = {1,3,6,8,10,15,22}; 
int *pointer = num; 
printf("*pointer:%d\n",*pointer); 
pointer++; 
printf("*pointer(p++):%d\n",*pointer);

Untagged unions


union type即聯(lián)合類型,之后的內(nèi)容會介紹。聯(lián)合類型中的不同類型的值會被存儲在同一地址,這也是不穩(wěn)定因素之一,所以擁有untagged unions的語言屬于weekly checked. 和untagged相對的是tagged union type, tagged union會用tag字段顯式地標記當(dāng)前正在使用的類型,因此要相對安全一些。
 

Weekly typed


一般弱類型語言屬于weekly checked

除此之外,往往是通過語言之間的比較來進行判斷,并沒有明顯的界限。類型系統(tǒng)除了提供類型檢查,保證程序的安全性之外,還可以提供信息給編譯器,以優(yōu)化執(zhí)行效率。同時,類型系統(tǒng)是對現(xiàn)實世界的抽象,可讀性高,也是更高級別抽象(如泛型,**OOP**s)的基礎(chǔ)。所以,無類型的語言在選型時基本會被排除掉。
 

強類型和弱類型


在語言的抉擇上,靜態(tài)檢查,動態(tài)檢查檢查范圍這幾個角度影響的是程序的穩(wěn)定性和執(zhí)行效率,那么開發(fā)效率呢?此時要引入另外一個維度,強類型弱類型,強類型是指一旦被指定類型,不可變,弱類型則可變,可變也分為隱式和顯式兩種。

 
// js 弱類型,隱式可變 
x = 1 
y = "2" 
z = x + y

弱類型提升了我們的開發(fā)效率。

總結(jié),在語言的抉擇上,我們可以從類型系統(tǒng)的靜態(tài)檢查,動態(tài)檢查,檢查范圍及類型是否可變這幾個維度來考慮,在開發(fā)效率,執(zhí)行效率及安全性之間做權(quán)衡。無類型的語言可以很安全,但是可維護性差,基本被排除在系統(tǒng)開發(fā)之外。

看到這里,你會發(fā)現(xiàn)文中并沒有講什么是動態(tài)類型語言,靜態(tài)類型語言。因為這個維度并不是影響我們抉擇的本質(zhì)因素。動態(tài)類型語言其實指的就是只有動態(tài)檢查的語言,靜態(tài)類型語言指擁有靜態(tài)檢查的語言,此類型非彼類型,個人覺得是翻譯的鍋。
 

類型推斷


類型推斷(type inference)是類型系統(tǒng)中推測一段代碼(聲明,表達式等)的類型的過程。類型推斷讓我們在編寫代碼時能夠略去類型的聲明,并且不會影響到類型檢查。如果值的類型僅在運行時才能被確定,這類語言被稱為動態(tài)語言(dynamically typed), 同樣的,如果值的類型僅在編譯時被確定,這類語言被稱為靜態(tài)語言(statically typed).
 

類型理論


類型理論涉及到的內(nèi)容和類型系統(tǒng)會有部分重疊,你可以理解為,類型理論是服務(wù)于類型系統(tǒng)的,即,我們使用了哪些理論來構(gòu)建我們的類型系統(tǒng),或者說該語言的類型系統(tǒng)實現(xiàn)了哪些特性。一門語言的類型系統(tǒng)一般只會實現(xiàn)其中的某幾種特性,這也是語言之爭的根源。
 

Polymorphism type


polymorphism翻譯為多態(tài)性,但不單單指面向?qū)ο罄锏亩鄳B(tài),而是指類型系統(tǒng)里的多態(tài)性質(zhì)。編譯時多態(tài),是在編譯時就能推導(dǎo)出類型或調(diào)用關(guān)系,宏也是一種編譯時多態(tài)。運行時多態(tài)的實現(xiàn)依賴于虛函數(shù)機制(virtual function), 是在運行時確定調(diào)用關(guān)系。多態(tài)性質(zhì)的引入可以提高代碼的復(fù)用率。

  1. Ad hoc polymorphism: 一個函數(shù)會根據(jù)有限的類型組合而擁有不同的實現(xiàn),函數(shù)重載(function overloading)和操作符重載(operator overloading)依賴于此. 從polymorphism性質(zhì)實現(xiàn)的角度講,屬于編譯時多態(tài)(static polymorphism).

     
    // java 
    public int Add(int a, int b) { 
    return a + b; 
    } 
    public String Add(String a, String b, String c) { 
    return a + b +c; 
    }
  2. Parametric polymorphism: 聲明的類型未被指定為某一類型,而在實現(xiàn)時可以指定為任意類型,即通常我們所說的泛型,在C++里稱為模板(template). 從polymorphism性質(zhì)實現(xiàn)的角度講,屬于編譯時多態(tài)(static polymorphism).

     
    // java 
    ... 
    public class ObjectsServiceFactory { 
    public T save(T o) throws Exception { 
    try { 
    return repository.save(o); 
    } catch (Exception e) { 
    throw new DatabaseOperationException(String.format("save object %s failed, %s", o.toString(), e.getMessage())); 
    } 
    } 
    public void delete(T o) { 
    repository.delete(o); 
    } 
    } 
    ... 
    ObjectsServiceFactory s = new ObjectsServiceFactory(); 
    ObjectsServiceFactory i = new ObjectsServiceFactory(); 
    s.save("demo"); 
    i.save(100)
  1. Subtype polymorphism: 也叫subtyping, 一個類型的變量可以指代多個該類的子類實例,即我們常說的多態(tài)。從多態(tài)性質(zhì)實現(xiàn)的角度講,此類屬于運行時多態(tài)(dynamic polymorphism).

     
    // c++ 
    #include 
    using namespace std; 
    class Animal { 
    public : 
    virtual void shout() = 0; 
    virtual ~Animal(){} 
    }; 
    class Dog :public Animal { 
    public: 
    virtual void shout(){ cout << "dog"<shout(); 
    animal2->shout(); 
    animal3->shout(); 
    delete(animal1); 
    delete(animal2); 
    delete(animal3); 
    return 0; 
    }
  2. Row polymorphism: 也叫duck typing,針對結(jié)構(gòu)體類型,從功能(purpose)的角度對類型歸類。通常,對象是根據(jù)它們的類型來確定彼此之間的關(guān)系,比如subtyping中的父類/子類關(guān)系,而duck typing是通過函數(shù),如果它們實現(xiàn)了相同的函數(shù),就認為它們是同一類。

    If it walks like a duck and it quacks like a duck, then it must be a duck.

    如果它走路像鴨子(實現(xiàn)了walk()函數(shù)),也會像鴨子一樣發(fā)出嘎嘎聲(實現(xiàn)了gaga())函數(shù),那它就是一只鴨子(屬于同一類型)。

     
    # python 
    class Duck: 
    def fly(self): 
    print("Duck flying") 
    class Airplane: 
    def fly(self): 
    print("Airplane flying") 
    class Whale: 
    def swim(self): 
    print("Whale swimming") 
    def lift_off(entity): 
    entity.fly() 
    duck = Duck() 
    airplane = Airplane() 
    whale = Whale() 
    lift_off(duck) # prints `Duck flying` 
    lift_off(airplane) # prints `Airplane flying` 
    lift_off(whale) # Throws the error `'Whale' object has no attribute 'fly'`

    duck typing也是go語言的主要特性,但是嚴格來說并不算,因為duck typing發(fā)生在運行時,且沒有顯式的interface聲明,上面的python示例就是典型的duck typing

  3. Polytypism: 函數(shù)式編程語言里的泛型特性。以Haskell為例,其函數(shù)的定義比較具體化,單一化,缺乏可擴展性和高度復(fù)用性,在Haskell語言上可以引入一種泛型機制解決上述問題,這種泛型機制主要體現(xiàn)在泛型函數(shù)的定義上,泛型函數(shù)的定義不同于以往的函數(shù)定義方法,當(dāng)泛型函數(shù)遇到某種未定義的類型參數(shù)時,它依靠泛型算法分析參數(shù)類型的結(jié)構(gòu),進行相關(guān)轉(zhuǎn)換,可以自動生成函數(shù)定義,這種方法可以提高程序的復(fù)用程度。[2]
     

Dependent types


依賴類型(或依存類型,dependent type)是指依賴于值的類型, 此特性通過極其豐富的類型表達能力使得程序得以借助類型的形式被檢查,從而有效減少程序錯誤。依賴類型的兩個常見實例是依賴函數(shù)類型(又稱依賴乘積類型, Π-類型)和依賴值對類型(又稱依賴總和類型Σ-類型)。[4]

一個依賴函數(shù)的返回值的類型可以依賴于某個參數(shù)的具體值,而非僅僅參數(shù)的類型,例如,一個輸入?yún)?shù)為整型值n的函數(shù)可能返回一個長度為n的數(shù)組

 
// Idris 
// 連接兩個列表 
// Vect n a 是依賴函數(shù)類型,a是列表元素的類型,n是輸入?yún)?shù),Vect n a 返回一個長度為n的列表 
app : Vect n a -> Vect m a -> Vect (n + m) a

一個依賴值對類型中的第二個值可以依賴于第一個值,例如,可表示一對這樣的值:它由一對整數(shù)組成,其中的第二個數(shù)總是大于第一個數(shù)。

 
def do(i : {i:Int | i<=j}, j : Int) := 
// do something
 
do(10, 1) // compile error
 
do(1, 10) // ok

以依賴類型系統(tǒng)為基礎(chǔ)的編程語言大多同時也作為構(gòu)造證明與可驗證程序的輔助工具而存在,如 Coq 和 Agda(但并非所有證明輔助工具都以類型論為基礎(chǔ))。近年來,一些以通用和系統(tǒng)編程為目的的編程語言被設(shè)計出來,如 Idris。
 

Linear types


Linear types的思想來源于Linear Logic, 它確保對象在程序運行期間有且僅有一個它的引用,這種類型用來描述不能被修改的值,比如文件描述符。linear 類型系統(tǒng)允許引用,但不允許別名(被多個變量引用), 類似于C++的unique_ptr指針, 只能被移動,不能被復(fù)制。
 

Intersection types


一個intersection type(交叉類型)是多個type的結(jié)合, 以此,你能夠得到一個包含多個類型的所有成員(members)的新類型!比如,現(xiàn)有三個類Person, Serializable 和 Loggable, 新的類型 T = Person & Serializable & Loggable, 那么類型T擁有Person,Serializable及Loggable的所有成員。

 
// TypeScript mixin example 
function extend(first: T, second: U): T & U { 
let result = {}; 
for (let id in first) { 
(result)[id] = (first)[id]; 
} 
for (let id in second) { 
if (!result.hasOwnProperty(id)) { 
(result)[id] = (second)[id]; 
} 
} 
return result; 
} 
class Person { 
constructor(public name: string) { } 
} 
interface Loggable { 
log(): void; 
} 
class ConsoleLogger implements Loggable { 
log() { 
console.log("papapa!"); 
} 
} 
var jim = extend(new Person("Jim"), new ConsoleLogger()); 
var n = jim.name; 
jim.log();

Union types


學(xué)過C語言的對此類型并不陌生,和intersection type類似,一個union type可以為多個類型,但是在任意時刻,它的值的類型只能是其中所有類型中的一種。

 
//c 
union a_bc { 
int i; 
char mm; 
};

TypeScript里的聯(lián)合類型用豎線|來分隔每個類型,所以 value : number | string | boolean表示一個值可以是 number,或string,或 boolean。

 
/** 
* TypeScript 
* Takes a string and adds "padding" to the left. 
* If 'padding' is a string, then 'padding' is appended to the left side. 
* If 'padding' is a number, then that number of spaces is added to the left side. 
*/ 
function padLeft(value: string, padding: string | number) { 
if (typeof padding === "number") { 
return Array(padding + 1).join(" ") + value; 
} 
if (typeof padding === "string") { 
return padding + value; 
} 
} 
let indentedString = padLeft("Hello world", true); // errors during compilation 
let ok = padLeft("Hello world", 0) // compile ok

Existential types


在理解存在類型(existential type)之前,我們先看下java的類型通配符 ? , 它代表一個未知類型.
 

Upper Bounded Wildcards


通過聲明通配的上限(父類)來匹配,如果你的函數(shù)入?yún)⒖赡苁荓ist\, List\或者 List\ ,你可以使用?聲明

 
public static void add(List list)

Number的所有子類都可以作為入?yún)? 例如:

 
//Java program to demonstrate Upper Bounded Wildcards 
import java.util.Arrays; 
import java.util.List; 
class WildcardDemo 
{ 
public static void main(String[] args) 
{ 
//Upper Bounded Integer List 
List list1= Arrays.asList(4,5,6,7); 
//printing the sum of elements in list 
System.out.println("Total sum is:"+sum(list1)); 
//Double list 
List list2=Arrays.asList(4.1,5.1,6.1); 
//printing the sum of elements in list 
System.out.print("Total sum is:"+sum(list2)); 
} 
private static double sum(List list) 
{ 
double sum=0.0; 
for (Number i: list) 
{ 
sum+=i.doubleValue(); 
} 
return sum; 
} 
}

省略寫法也是Upper Bounded

 
Collection c = new ArrayList(); 
c.add(new Object()); // compile error

當(dāng)類型不可推斷時,上限是Object

 
List

Lower Bounded Wildcards


通過聲明通配的下限(子類)來匹配, 比如函數(shù)的入?yún)⒙暶鳛?

 
List

那么,Integer類型的所有父類都可以作為入?yún)ⅰ@?

 
//Java program to demonstrate Lower Bounded Wildcards 
import java.util.Arrays; 
import java.util.List; 
class WildcardDemo 
{ 
public static void main(String[] args) 
{ 
//Lower Bounded Integer List 
List list1= Arrays.asList(4,5,6,7); 
//Integer list object is being passed 
printOnlyIntegerClassorSuperClass(list1); 
//Number list 
List list2= Arrays.asList(4,5,6,7); 
//Integer list object is being passed 
printOnlyIntegerClassorSuperClass(list2); 
} 
public static void printOnlyIntegerClassorSuperClass(List list) 
{ 
System.out.println(list); 
}

那java的類型通配符和存在類型有什么關(guān)系呢?先看看scala的締造者Martin Odersky 對scala引入存在類型的回答

Bill Venners: Existential types were added to Scala relatively recently. The justification I heard for existentential types was that they allow you to map all Java types, in particular Java's wildcard types, to Scala types. Are existential types larger than that? Are they a superset of Java's wildcard types? And is there any other reason for them that people should know about?

Martin Odersky: It is hard to say because people don't really have a good conception of what wildcards are. The original wildcard design by Atsushi Igarashi and Mirko Viroli was inspired by existential types. In fact the original paper had an encoding in existential types. But then when the actual final design came out in Java, this connection got lost a little bit. So we don't really know the status of these wildcard types right now.

Martin Odersky 在scala郵件組里的回答

The original Java wildcard types (as described in the ECOOP paper by
Igarashi and Viroli) were indeed just shorthands for existential
types. I am told and I have read in the FOOL ’05 paper on Wild FJ that
the final version of wildcards has some subtle differences with
existential types. I would not know exactly in what sense (their
formalism is too far removed from classical existential types to be
able to pinpoint the difference), but maybe a careful read of the Wild
FJ paper would shed some light on it.

可見,java類型通配符的設(shè)計思想來源于存在類型,但有些不同。scala引入存在類型是為了兼容java和jvm,所以會比?更強大。那么既然區(qū)別不大,理解了類型通配即理解了存在類型。這是一個曲線救國的方式。

接下來,我們從數(shù)學(xué)定義的角度來理解。

existential type里的existential來源于存在量詞, 在謂詞邏輯中的解釋:

x: P(x) 表示存在至少一個 x 使得 P(x) 為真。

存在類型的公式化表示:T = ∃X { X a; int f(X); }

類型X是存在類型, 即存在一個類型X,滿足此表達式,在編程語言里我們稱之為可實現(xiàn)。存在類型適合用來定義接口,不論是模塊之間還是語言之間。

這里要提下泛型(即前面講到的Parametric polymorphism, 也叫Universal type), 以避免混淆。Universal type中的universal來源于全稱量詞, 在謂詞邏輯中的解釋:
∀ x: P(x) 表示 P(x) 對于所有 x 為真。

泛型的公式化表示:

 T = ∀X { X a; int f(X); }

即,對于所有類型X,滿足此表達式。
 

語言規(guī)范


在語言學(xué)里有三個基本概念(也是三個分支),syntax, semantics, pragmatics. 即語法,語義(編譯結(jié)果)和語用(最佳實踐, 標準庫, 生態(tài))。這里主要講語法層面。
 

Types


一門語言的規(guī)范,首先是類型(type), 聲明(statements), 表達式(expressions)等, 然后是作用域(scoping)。前面的內(nèi)容介紹了類型系統(tǒng), 那么該類型系統(tǒng)定義了哪些類型,實現(xiàn)了哪些特性是我們首先要了解的。通常一門語言的語法規(guī)范會以grammar[6]的形式來定義,例如golang中對于浮點數(shù)字的描述:

 
float_lit = decimals "." [ decimals ] [ exponent ] | 
decimals exponent | 
"." decimals [ exponent ] . 
decimals = decimal_digit { decimal_digit } . 
exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .

這段文本描述了浮點數(shù)字的書寫規(guī)則,也是一棵語法樹。grammar對于沒有學(xué)過的人來說有些難懂,所以本文中用類型實例的方式表示。
 

Primitive types


即基礎(chǔ)類型, 或者叫內(nèi)置(builtin)類型, 是程序以及復(fù)合類型的創(chuàng)建基礎(chǔ)。


Compound types


即復(fù)合類型, 也叫*Composite type*s. 復(fù)合類型可以由基礎(chǔ)類型和復(fù)合類型所構(gòu)成。


Statements


statement的直譯是聲明,但在這里按照代碼的邏輯單元來理解,一個statement是邏輯單元的開始或結(jié)束。在書籍中,通常描述為xxx語句,比如if語句。
 

Empty statement


空語句, 不做任何事情。grammar規(guī)則:

 
EmptyStatement: 
;

Labeled statement


標簽語句, 通常作為goto, break, continue 的目標,例如c/c++里的goto語句,一個單詞加上冒號即是labeled statement。下面代碼中的標簽語句LOOP:即是goto的目標。

 
/*c*/ 
#include  
int main () { 
/* local variable definition */ 
int a = 10; 
/* do loop execution */ 
LOOP:do { 
if( a == 15) { 
/* skip the iteration */ 
a = a + 1; 
goto LOOP; 
} 
printf("value of a: %d\n", a); 
a++; 
}while( a < 20 ); 
return

它的grammar規(guī)則為:

 
LabeledStmt = Label ":" Statement . 
Label = identifier .

labeled statement作為goto的目標大家應(yīng)該見的很多,這里再舉一個作為continue的目標的例子,便于理解。

 
 // golang 
guestList := []string{"bill", "jill", "joan"} 
arrived := []string{"sally", "jill", "joan"} 

CheckList: 
for _, guest := range guestList { 
for _, person := range arrived { 
fmt.Printf("Guest[%s] Person[%s]\n", guest, person) 

if person == guest { 
fmt.Printf("Let %s In\n", person) 
continue CheckList 
} 
} 
}

Expression statement


某些expression(表達式), 比如賦值,函數(shù)調(diào)用等,可以作為一個statement,稱之為expression statement
 

If statement


我們常說的if條件語句,也叫If-then-else, 如果條件滿足則執(zhí)行此邏輯, 否則執(zhí)行它的else(如果存在)邏輯.

 
// golang 
if x > max { 
x = max 
}

Assert statement


斷言語句,assert 加真假值表達式, 如果表達式結(jié)果為false, 程序退出。

 
// c 
assert( size <= LIMIT );

不是所有語言都有斷言語句.
 

Switch statement


switch條件語句,判斷表達式的值,滿足不同的條件執(zhí)行時執(zhí)行不同的邏輯, 當(dāng)所有條件都不滿足時,執(zhí)行默認邏輯(如果存在).

 
switch(expression) { 
case n: 
code block 
break; 
case n: 
code block 
break; 
default: 
code block 
}

對于golang, switch statement分成了兩類, 一類是常規(guī)的expression switch(上面的例子), 一類是type switch(下面的例子)

 
// golang type switch 
switch i := x.(type) { 
case nil: 
printString("x is nil") // type of i is type of x (interface{}) 
case int: 
printInt(i) // type of i is int 
case float64: 
printFloat64(i) // type of i is float64 
case func(int) float64: 
printFunction(i) // type of i is func(int) float64 
case bool, string: 
printString("type is bool or string") // type of i is type of x (interface{}) 
default: 
printString("don't know the type") // type of i is type of x (interface{}) 
}

While statement


while循環(huán)語句, 重復(fù)判斷bool表達式的值,如果為真則執(zhí)行,直到表達式的值為假

 
// c 
while(condition) 
{ 
statement(s); 
}

tip: 在golang里,for語句加上bool表達式,可以實現(xiàn)while語句

 
// golang 
for { 
// code block 
} 
for a < b { 
f() 
}

Do statement


do語句,和while配合使用,先執(zhí)行一次邏輯,再判斷條件

 
// c 
#include  
int main(void){ 
int sum=0,i; 
scanf("%d",&i); 
do{ 
sum=sum+i; 
i++; 
} 
while(i<=10); 
printf("sum=%d",sum); 
return 0; 
}

For statement


for語句, 一般由三部分組成,初始化表達式,條件表達式和一個末尾表達式(post statement),初始化表達式只在開始時執(zhí)行一次,條件表達式用來判斷本次執(zhí)行是否退出,末尾表達式在每次for語句的代碼邏輯執(zhí)行完后都會執(zhí)行。

 
// golang 
for i := 0; i < 10; i++ { 
f(i) 
}

For-in statement


for語句常用來迭代,于是出現(xiàn)了多個變種,for-in常見于腳本語言,如TypeScipt,Groovy, 用于迭代可枚舉的(enumerable types)數(shù)據(jù)類型

 
// JavaScript 
var person = {fname:"John", lname:"Doe", age:25}; 
var text = ""; 
var x; 
for (x in person) { 
text += person[x] + " "; 
}

這里,x被賦值為person這個map的key。當(dāng)遍歷的對象是map時,x為key,當(dāng)遍歷的對象是數(shù)組時,x為索引下標
 

For-of statement


for語句變種, 類似于for-in, 用來迭代可迭代的(iterable)數(shù)據(jù)類型, 比如數(shù)組和字符串

 
// JavaScript 
function* foo(){ 
yield 1; 
yield 2; 
} 

for (let v of foo()) { 
console.log(v); 
// expected output: 1 

break; // closes iterator, triggers return 
}

這里變量v被賦值為foo函數(shù)返回的對象。被賦值的變量總是可迭代類型里的元素。

For-in vs For-of


For-range statement


for語句變種,在golang中用來迭代數(shù)組, 或者map

 
// golang 
nums := []int{2, 3, 4} 
sum := 0 
for _, num := range nums { 
sum += num 
} 

kvs := map[string]string{"a": "apple", "b": "banana"} 
for k, v := range kvs { 
fmt.Printf("%s -> %s\n", k, v) 
}

Break statement


跳出語句,用于立即跳出一個邏輯單元,當(dāng)不配合labeled statement使用時,立即(abruptly)跳出最里層的一個封閉(enclosing)邏輯單元, 如switch, do, while, for. 當(dāng)配合labeled statement使用時,立即跳出label標定的層級的封閉邏輯單元。

 
// c 
#include  
int main () 
{ 
/* 局部變量定義 */ 
int a = 10; 
/* while 循環(huán)執(zhí)行 */ 
while( a < 20 ) 
{ 
printf("a 的值: %d\n", a); 
a++; 
if( a > 15) 
{ 
/* 使用 break 語句終止循環(huán) */ 
break; 
} 
} 
return 0; 
}

labeled break已在labeled statement中說明,這里不再贅述.
 

Continue statement


continue語句,立即結(jié)束當(dāng)前層級的邏輯,跳轉(zhuǎn)到for循環(huán)的末尾表達式,開始下一次循環(huán).

 
// golang 
package main 

import "fmt" 

func main() { 
rows := []int{1, 3, 5} 
colunms := []int{2, 4, 6} 

for _, row := range rows { 
for _, column := range colunms { 
if column == 4 { 
continue 
} 
fmt.Printf("%d-%d\n", row, column) 
} 
} 
}

上述代碼的輸出為:

 
1-2 
1-6 
3-2 
3-6 
5-2 
5-6

continuelabeled statement配合使用時,不僅會結(jié)束當(dāng)前層級的邏輯,還會跳轉(zhuǎn)到label標簽指定的位置。我們看下下面的代碼邏輯.

 
// golang 
package main 

import "fmt" 

func main() { 
rows := []int{1, 3, 5} 
colunms := []int{2, 4, 6} 

RowLoop: 
for _, row := range rows { 
for _, column := range colunms { 
if column == 4 { 
continue RowLoop 
} 
fmt.Printf("%d-%d\n", row, column) 
} 
} 
}

當(dāng)column等于4時,結(jié)束邏輯,此時不是跳轉(zhuǎn)到當(dāng)前層級的post statement, 而是跳轉(zhuǎn)到RawLoop, 所以輸出結(jié)果應(yīng)該為:

 
1-2 
3-2 
5-2

不僅4沒有輸出,6也被continue掉了.
 

Return statement


return語句跳出當(dāng)前函數(shù),回到函數(shù)的調(diào)用方, 同時將一個或者多個返回值傳給調(diào)用方。本應(yīng)出現(xiàn)在最后一行的return語句,在沒有返回值的情況下,可以省略。Groovy語言的return語句是可選的。

 
 // sum method 
def static sum(n, closure) { 
for(int i = 2; i <= n; i += 2) { 
closure(i) 
} 
// return 2, 可以簡寫成2 
2 
}

Throw statement


在一些語言中比如Java, JavaScript等使用throw語句來拋出錯誤,以便上層的調(diào)用方能夠通過try-catch-throw的方式捕捉并處理。未捕捉的throw語句會導(dǎo)致線程/進程終止。對于Java, throw的的對象必須是Exception或者其子類,對于JavaScript, throw的對象可以是任意類型.

 
// JavasScript 
function getRectArea(width, height) { 
if (isNaN(width) || isNaN(height)) { 
throw "Parameter is not a number!"; 
} 
} 

try { 
getRectArea(3, 'A'); 
} 
catch(e) { 
console.log(e); 
// expected output: "Parameter is not a number!" 
}

Goto statement


goto語句和labeled statement配合使用,用于邏輯跳轉(zhuǎn),程序執(zhí)行流程會直接跳轉(zhuǎn)到標簽處. goto statement只有部分語言提供,而且寫法也有不同,比如對于golang, labeled statement必須在goto statement之前, 而C語言則無此限制。

 
// c 
#include  

int main () 
{ 
/* 局部變量定義 */ 
int a = 10; 

/* do 循環(huán)執(zhí)行 */ 
LOOP:do 
{ 
if( a == 15) 
{ 
/* 跳過迭代 */ 
a = a + 1; 
goto LOOP; 
} 
printf("a 的值: %d\n", a); 
a++; 

}while( a < 20 ); 

return 0; 
}

以上都是常見的語句,除此之外,語言也會有實現(xiàn)一些非常規(guī)的語句,比如golangdefer語句。
 

Operators


在介紹表達式之前,先介紹它的組成元素之一,操作符。其中,操作符分為一元(unary)操作符,二元(binary)操作符和三元操作符. 優(yōu)先級決定了在多個操作符同時出現(xiàn)時,先使用哪個來求值。數(shù)字越大,優(yōu)先級越高。
 

Unary operators



Binary operators



Ternary operators


在計算機中也叫條件運算符(conditional operator)


Expressions


在編程語言里一個表達式(expression[7])是由一個或多個常量,變量,操作符或函數(shù)組成。通過對表達式求值(evaluate)來得到一個新的值,這個新的值可以是基礎(chǔ)類型,也可以是復(fù)合類型。表達式在運算時,會進行類型推斷和類型檢查。從之前講的內(nèi)容可以看出,statements的作用主要是控制代碼邏輯的執(zhí)行順序,expressions則是具體的代碼邏輯.
 

Variable expression

 
// golang 
c := a + b

Arithmetic expression

 
2 + 3

Relational expression

 
3 != 4

Function expression

 
// golang 
v := f(1)

Index expression

 
a[x]

表達式并沒有嚴格的分類,各個語言也不盡相同,這里僅列舉了部分例子來說明。
 

Scoping


即作用域, 作用域是名稱(比如變量的聲明)和其實體(entity, 比如變量的定義)的綁定規(guī)則。作用域約束了實體的作用范圍,保證程序是無歧義的。
 

Expression scope


實體僅在表達式內(nèi)可用。

 
// c 
({ int x = f(); x * x; })

臨時變量x接受函數(shù)的返回值并平方,這樣避免兩次調(diào)用函數(shù)f.
 

Block scope


通常編程語言都會使用花括號{}來將代碼包裹成塊(block), 在block內(nèi)聲明的實體,僅在block內(nèi)有效。

 
// golang 
{ 
var a int 
b := a 
} 
b := a // compile error
 
// golang 
{ 
var a int 
b := a 
} 
b := 1 // ok

Function scope


在函數(shù)內(nèi)聲明的實體,僅在函數(shù)內(nèi)有效。

 
def square(n): 
return n * n 

def sum_of_squares(n): 
total = 0 
i = 0 
while i <= n: 
total += square(i) 
i += 1 
return total

為了不和block scope混淆,這里用python的例子。
 

File scope


在代碼文件內(nèi)聲明的全局變量,僅在當(dāng)前文件內(nèi)有效。
 

Module scope


在某些現(xiàn)代語言中,一個實體可以在一個模塊內(nèi)的各個文件內(nèi)有效,比如golang. 部分語言,一個文件就是一個獨立的module,此時,也屬于file scope.
 

Global scope


在所有模塊,所有文件內(nèi)都有效的實體稱為全局實體,此類作用域?qū)儆?em>global scope. 在編程實踐當(dāng)中,應(yīng)盡量避免使用。
 

Packages


一個復(fù)雜程序一般會由多個包(或者叫模塊)組成, 這種機制能讓程序的結(jié)構(gòu)和邏輯更加清晰可讀,提高代碼的復(fù)用能力,也可以借助module scope來避免同名之間的沖突。這里僅列舉幾種常見的包引入方式。

 
// Java 包名是一個層級結(jié)構(gòu) 
import org.apache.commons.net.util.SubnetUtils; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.security.core.context.SecurityContextHolder; 
import org.springframework.stereotype.Service; 
import org.springframework.util.StringUtils; 

Subnetutils sbt = new Subnetutils("xx")
 
// python 
from sys import argv

順便說下python里from和常規(guī)import之間,在包的的使用上會有差異。

 
>>> from os import path 
>>> path. 
File "", line 1 
path. 
^ 
SyntaxError: invalid syntax 
>>> path 
 
>>> os.path 
Traceback (most recent call last): 
File "", line 1, in  
NameError: name 'os' is not defined 
>>> import os.path 
>>> os.path 
 
>>>
 
// node 
var circle = require('./circle.js'); 
console.log( 'The area of a circle of radius 4 is ' + circle.area(4));
 
// golang 
import ( 
"fmt" 
"github.com/xx/xx" 
)
 
// TypeScript 
import { cube, foo } from './mylib'; 
console.log(cube(3)); 
console.log(foo);
 
// c/c++ 
#include "my_header.h" 

int foo(char* name) { 
//do stuff 
return 0; 
}

編程范式


編程范式(programming paradigms),是在編程的理論與實踐當(dāng)中提煉出的概念模型。

Programming Paradigm: A conceptual model underlying the theories and practice of programming
 

IP(Imperative Programming)


即指令式編程。程序由一系列指令和流程控制語句組成,運行過程中指令不斷改變程序的狀態(tài),由此達到最終的編碼意圖。IP范式會顯式地指定代碼的執(zhí)行流程,以及運算邏輯。匯編是典型的使用IP范式的編程語言。

 
 result = [] 
i = 0 
start: 
numPeople = length(people) 
if i >= numPeople goto finished 
p = people[i] 
nameLength = length(p.name) 
if nameLength <= 5 goto nextOne 
upperName = toUpper(p.name) 
addToList(result, upperName) 
nextOne: 
i = i + 1 
goto start 
finished: 
return sort(result)

SP(Structured Programming)


即結(jié)構(gòu)化編程,在IP的基礎(chǔ)上,我們可以將用goto來控制流程的代碼,以for語句,while語句等此類結(jié)構(gòu)化的代碼塊(block structure)組織起來,使得代碼的可讀性更高,那么此種編碼方式即為結(jié)構(gòu)化范式。SP是現(xiàn)代語言都支持的一種基礎(chǔ)范式。

 
result = []; 
for i = 0; i < length(people); i++ { 
p = people[i]; 
if length(p.name)) > 5 { 
addToList(result, toUpper(p.name)); 
} 
} 
return sort(result);

PP(Procedure Programming)


即過程式編程,單看中文可能難以理解,procedure來源于procedure call, 即函數(shù)調(diào)用,主要是因為PPIP的基礎(chǔ)上引入了函數(shù)及函數(shù)調(diào)用, 將可提煉的邏輯用函數(shù)封裝起來,以復(fù)用代碼和提高可讀性。

OOP(Object-oriented Programming)

即我們常說的面向?qū)ο缶幊。?strong>SP和PP的范疇里,數(shù)據(jù)類型是松散的,數(shù)據(jù)結(jié)構(gòu)和算法之間也是松散的,而OOP則提供了一種類似于人類對現(xiàn)實世界建模的方法,對二進制世界的類型和邏輯進行建模和封裝,并在此基礎(chǔ)上提供了更多的類型和語法特性。OOP的優(yōu)點簡列如下(封裝,繼承,多態(tài)):

  • 當(dāng)我們對一組數(shù)據(jù)類型進行抽象,封裝成類(class, 類是OOP的基本概念)時,我們可以定義該類的子類,來共享它的數(shù)據(jù)類型和邏輯,此方式稱為繼承(inheritance), 能夠有效減少我們的開發(fā)時間。
  • 當(dāng)類被定義后,通常它只需要關(guān)注它自身的數(shù)據(jù)和邏輯,通過語法特性,一般是public/private關(guān)鍵字,將這類數(shù)據(jù)和邏輯隱藏起來,避免被非法(或者說不合理的, 不應(yīng)當(dāng)?shù)?訪問,提升程序和系統(tǒng)的安全性。
  • 一個封裝好的類,不僅能被它的創(chuàng)建者使用,也可以分發(fā)(在網(wǎng)絡(luò)上)給其他人使用, 比如Java的jar包。
  • 一門語言不可能把開發(fā)者所需要的所有的類型都定義好,class的概念則很好地解決了這個問題,開發(fā)者可以定義任意自己想要的數(shù)據(jù)類型。
  • OOP的多態(tài)性質(zhì)可以讓代碼更加靈活。


DP(Declarative Programming)


即,描述性范式。和IP相反,此類語言只描述最終的編碼意圖,不描述達到意圖的過程。舉個例子,如何用程序來回答你是怎么回家的?

  • IP:

    Go out of the north exit of the parking lot and take a left. Get on I-15 south until you get to the Bangerter Highway exit. Take a right off the exit like you’re going to Ikea. Go straight and take a right at the first light. Continue through the next light then take your next left. My house is #298.

  • DP:

    My address is 298 West Immutable Alley, Draper Utah 84020

典型的DP范式語言如SQL, 僅描述目的,達到目的的邏輯被隱藏。

 
SELECT * FROM Users WHERE Country=’Mexico’;

LP(Logic Programming)


即邏輯編程,它屬于DP的范疇。邏輯編程的要點是將數(shù)學(xué)中的邏輯風(fēng)格帶入計算機程序設(shè)計之中。它設(shè)置匹配規(guī)則來解決問題(rule-based),而非設(shè)置步驟來解決問題, 即事實+規(guī)則=結(jié)果。Prolog是典型的LP范式語言,此類語言主要應(yīng)用在人工智能,專家系統(tǒng)等領(lǐng)域。
 

FP(Functional Programming)


即函數(shù)式編程,也是DP的子集, 在函數(shù)式編程里,所有的計算都是通過函數(shù)調(diào)用完成的,函數(shù)里的SP邏輯尤其是控制流邏輯,被隱藏了起來. 假設(shè)我們要編寫一個函數(shù),將一個數(shù)組的每個元素都乘以2,PP風(fēng)格的代碼如下:

 
// TypeScript 
function double (arr) { 
let results = [] 
for (let i = 0; i < arr.length; i++){ 
results.push(arr[i] * 2) 
} 
return results 
}

上述代碼,詳細地寫明了整個計算過程,包括迭代過程和計算方法。所以IP范疇的范式會詳細描述計算機是如何完成這件事的,有篇文章是這么描述IP

First do this, then do that.

FP則不會描述數(shù)組是如何迭代的,也不會顯式地修改變量, 僅僅描述了我們想要什么,我們想要將元素乘以2.

 
function double (arr) { 
return arr.map((item) => item * 2) 
}

FP將開發(fā)者從機器的執(zhí)行模型切換到了人的思維模型上,可讀性會更高。需要注意的是,某些支持FP的語言本身是屬于IP的,同時也可以認為其屬于DP, 不必過于糾結(jié)。
 

FRP(Functional Reactive Programming)


即,函數(shù)式響應(yīng)型編程。
 

MP(Meta Programming)


即元編程, 也寫做Metaprogramming。元編程是一種可以將程序當(dāng)作數(shù)據(jù)來操作的技術(shù),元編程能夠讀取,生成,分析或轉(zhuǎn)換其他的程序代碼,甚至可以在運行時修改自身. C++的template即屬于meta programming的范疇,編譯器在編譯時生成具體的源代碼。在web框架Ruby on Rails里,元編程被普遍使用。比如,在SQL數(shù)據(jù)庫的表里,有一個表users,在ruby中用類User表示,你需要根據(jù)user的email字段來獲取相應(yīng)的結(jié)果,通常的做法是寫個sql查詢語句去完成,但是Ruby on Rails在元編程的加持下,會讓這件事變得異常簡單。

 
User.find_by_email('songtianyi630@163.com')

find_by_email并不是我們自己的定義的函數(shù),它是如何完成這件事的呢?框架會根據(jù)函數(shù)名find_by_email動態(tài)生成一個查詢函數(shù)去查詢數(shù)據(jù)庫。除了這種黑魔法,元編程還能夠動態(tài)修改一個類的成員函數(shù),在運行時創(chuàng)建函數(shù)等等,這里不展開講,在Ruby或者Groovy的相關(guān)書籍里會有詳細介紹。語言的反射特性和對模板的支持是實現(xiàn)元編程的主要基礎(chǔ),雖然c++并不支持反射,但c++的模板提供了元編程的能力。

編程范式還有很多細分項,比如Event-driven programming, Distributed programming[10]等, 這里不再一一列舉。
 

依賴管理


有了合適的類型系統(tǒng),且該語言支持了我們所需要的編程范式,那么它就是不二之選了嘛?不一定。

長期從事開發(fā),或者從事過大型項目開發(fā)的程序員會發(fā)現(xiàn)代碼的依賴管理也是開發(fā)和維護過程當(dāng)中的重點。好的依賴管理會降低我們的心智負擔(dān),也會降低業(yè)務(wù)風(fēng)險。

TODO
 

標準庫


TODO
 

選型參考


術(shù)語說明


  • static and dynamic checks: 指該語言的類型系統(tǒng)會進行靜態(tài)檢查和動態(tài)檢查,注意,所有語言都有動態(tài)檢查。
  • strongly checked: 以檢查范圍的大小作為標準,意味著類型系統(tǒng)的類型檢查(type checking)已經(jīng)盡可能消除了類型錯誤, 有些地方會稱為strongly typing, 有的地方甚至?xí)Q為strongly typed, 要注意區(qū)分.
  • weekly typed: 類型可變
  • strongly typed: 類型不可變
  • dynamically typed: 值的類型僅在運行時確定
  • statically typed: 值的類型僅在編譯時確定
  • duck: duck typing, 也叫row polymorphism
  • generic: parametric polymorphism
  • subtype: subtype polymorphism
  • overloading: Ad hoc polymorphism, 函數(shù)重載或操作符重載或兩者都有

整理完這個對比表才發(fā)現(xiàn)一些有意思的事情:

  • 原來大部分語言都有OOP, js也是.
  • Go的優(yōu)勢并不在自己的類型系統(tǒng)和語法特性上, 它的設(shè)計充分體現(xiàn)了less is more的設(shè)計哲學(xué), 上手十分簡單, 工程實踐效果很好。


參考文獻


  1. 《Type Systems》, Luca Cardelli
  2. 《函數(shù)式語言泛型特性的研究與實現(xiàn)》, LI Yang, YU Shangchao, WANG Peng
  3. 《Types and Programming Languages》, Benjamin C. Pierce
  4. 《依賴類型》, wikipedia
  5. 《what is dependent type?》, StackOverflow
  6. 《Antlr Docs》, github
  7. 《Expression (computer science)》, wikipedia
  8. 《List of programming languages by type》, wikipedia
  9. 《Comparison of programming languages》, wikipedia
  10. [《Comparison of multi-paradigm programming language》, wikipedia

標簽: isp seo 安全 代碼 腳本 開發(fā)者 數(shù)據(jù)庫 網(wǎng)絡(luò)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:大數(shù)據(jù)+人工智能正以八種方式撼動商界

下一篇:鉛酸蓄電池難以適應(yīng)數(shù)據(jù)中心的要求