青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆-341  評(píng)論-2670  文章-0  trackbacks-0

類型是了解編程語(yǔ)言的重要一環(huán)。就算是你喜歡動(dòng)態(tài)類型語(yǔ)言,為了想實(shí)現(xiàn)一個(gè)靠譜的東西,那也必須了解類型。舉個(gè)簡(jiǎn)單的例子,我們都知道+和-是對(duì)稱的——當(dāng)然這只是我們的愿望了,在javascript里面,"1"+2和"1"-2就不是一回事。這就是由于不了解類型的操作而犯下的一些滑稽的錯(cuò)誤。什么,你覺(jué)得因?yàn)?1"的類型是string所以"1"+2就應(yīng)該是"12"?啐!"1"的類型是(string | number),這才是正確的做法。

了解編程語(yǔ)言的基本原理并不意味著你一定要成為一名編譯器的前端,正如同學(xué)習(xí)Haskell可以讓你的C++寫得更好一樣,如果你知道怎么設(shè)計(jì)一門語(yǔ)言,那遇到語(yǔ)言里面的坑,你十有八九可以當(dāng)場(chǎng)看到,不會(huì)跳進(jìn)去。當(dāng)然了,了解編程語(yǔ)言的前提是你是一個(gè)優(yōu)秀的程序員,至少要寫程序,對(duì)吧。于是我這里推薦幾門語(yǔ)言是在此之前要熟悉的。編程語(yǔ)言有好多種,每一種都有其代表作,為了開開眼界,知道編程語(yǔ)言可以設(shè)計(jì)成什么樣子,你至少應(yīng)該學(xué)會(huì):

  1. C++
  2. C#
  3. F#
  4. Haskell
  5. Ruby
  6. Prolog

其實(shí)這一點(diǎn)也不多,因?yàn)橹皇菍W(xué)會(huì)而已,知道那些概念就好了,并不需要你成為一個(gè)精通xx語(yǔ)言的人。那為了了解類型你應(yīng)該學(xué)會(huì)什么呢?沒(méi)錯(cuò)——就是C++了!很多人可能不明白,為什么長(zhǎng)得這么難看的C++竟然有這么重要的作用呢?其實(shí)如果詳細(xì)了解了程序設(shè)計(jì)語(yǔ)言的基本原理之后,你會(huì)發(fā)現(xiàn),C++在除了兼容那個(gè)可憐的C語(yǔ)言之外的那些東西,是設(shè)計(jì)的非常科學(xué)的。當(dāng)然現(xiàn)在講這些還太早,今天的重點(diǎn)是類型。

如果你們?nèi)タ聪嚓P(guān)的書籍或者論文的話,你們會(huì)發(fā)現(xiàn)類型這個(gè)領(lǐng)域里面有相當(dāng)多的莫名其妙的類型系統(tǒng),或者說(shuō)名詞。對(duì)于第一次了解這個(gè)方面的人來(lái)說(shuō),熟練掌握Haskell和C++是很有用的,因?yàn)镠askell可以讓你真正明白類型在程序里面的重要做喲的同時(shí)。幾乎所有流行的東西都可以在C++里面找到,譬如說(shuō):

  1. 面向?qū)ο?#8594;class
  2. polymorphic type→template
  3. intersection type→union / 函數(shù)重載
  4. dependent type→帶數(shù)字的模板類型
  5. System F→在泛型的lambda表達(dá)式里面使用decltype(看下面的例子)
  6. sub typing的規(guī)則→泛型lambda表達(dá)式到函數(shù)指針的隱式類型轉(zhuǎn)換

等等等等,因有盡有,取之不盡,用之不竭。你先別批判C++,覺(jué)得他東西多所以糟糕。事實(shí)是,只要編譯器不用你寫,那一門語(yǔ)言是不可能通過(guò)拿掉feature來(lái)使它對(duì)你來(lái)說(shuō)變得更牛逼的。不知道為什么有那么多人不了解這件事情,需要重新去念一念《形式邏輯》,早日爭(zhēng)取做一個(gè)靠譜的人。

泛型lambda表達(dá)式是C++14(沒(méi)錯(cuò),是14,已經(jīng)基本敲定了)的內(nèi)容,應(yīng)該會(huì)有很多人不知道,我在這里簡(jiǎn)單地講一下。譬如說(shuō)要寫一個(gè)lambda表達(dá)式來(lái)計(jì)算一個(gè)容器里所有東西的和,但是你卻不知道容器和容器里面裝的東西是什么。當(dāng)然這種情況也不多,但是有可能你需要把這個(gè)lambda表達(dá)使用在很多地方,對(duì)吧,特別是你#include <algorithm>用了里面超好用的函數(shù)之后,這種情況就變得常見了。于是這個(gè)東西可以這么寫:

auto lambda = [](const auto& xs)
{
    decltype(*xs.begin()) sum = 0;
    for(auto x : xs)
    {
        sum += x;
    }
    return sum;
};

于是你就可以這么用了:

vector<int> a = { ... };
list<float> b = { ... };
deque<double> c = { ... };

int sumA = lambda(a);
float sumB = lambda(b);
double sumC = lambda(c);

然后還可以應(yīng)用sub typing的規(guī)則把這個(gè)lambda表達(dá)式轉(zhuǎn)成一個(gè)函數(shù)指針。C++里面所有中括號(hào)不寫東西的lambda表達(dá)式都可以被轉(zhuǎn)成一個(gè)函數(shù)指針的,因?yàn)樗緛?lái)就可以當(dāng)成一個(gè)普通函數(shù),只是你為了讓業(yè)務(wù)邏輯更緊湊,選擇把這個(gè)東西寫在了你的代碼里面而已:

doube(*summer)(const vector<double>&);
summer = lambda;

只要搞明白了C++之后,那些花里胡俏的類型系統(tǒng)的論文的概念并不難理解。他們深入研究了各種類型系統(tǒng)的主要原因是要做系統(tǒng)驗(yàn)證,證明這個(gè)證明那個(gè)。其實(shí)編譯器的類型檢查部分也可以當(dāng)成是一個(gè)系統(tǒng)驗(yàn)證的程序,他要檢查你的程序是不是有問(wèn)題,于是首先檢查系統(tǒng)。不過(guò)可惜的是,除了Haskell以外的其他程序語(yǔ)言,就算你過(guò)了類型系統(tǒng)檢查,也不見得你的程序就是對(duì)的。當(dāng)然了,對(duì)于像javascript這種動(dòng)態(tài)類型就罷了還那么多坑(ruby在這里就做得很好)的語(yǔ)言,得通過(guò)大量的自動(dòng)化測(cè)試來(lái)保證。沒(méi)有類型的幫助,要寫出同等質(zhì)量的程序,需要花的時(shí)間要更多。什么?你不關(guān)心質(zhì)量?你不要當(dāng)程序員了!是因?yàn)槔习宕叩锰o?我們Microsoft最近有招聘了,快來(lái)吧,可以慢慢寫程序!

不過(guò)正因?yàn)榫幾g器會(huì)檢查類型,所以我們其實(shí)可以把一個(gè)程序用類型武裝起來(lái),使得錯(cuò)誤的寫法會(huì)變成錯(cuò)誤的語(yǔ)法被檢查出來(lái)了。這種事情在C++里面做尤為方便,因?yàn)樗С謉ependent type——好吧,就是可以在模板類型里面放一些不是類型的東西。我來(lái)舉一個(gè)正常人都熟練掌握的例子——單位。

一、類型檢查(type rich programming)

我們都知道物理的三大基本單位是米、秒和千克,其它東西都可以從這些單位拼出來(lái)(大概是吧,我忘記了)。譬如說(shuō)我們通過(guò)F=ma可以知道力的單位,通過(guò)W=FS可以知道功的單位,等等。然后我們發(fā)現(xiàn),單位之間的關(guān)系都是乘法的關(guān)系,每個(gè)單位還帶有自己的冪。只要弄清楚了這一點(diǎn),那事情就很好做了。現(xiàn)在讓我們來(lái)用C++定義單位:

template<int m, int s, int kg>
struct unit
{
    double value;

    unit():value(0){}
    unit(double _value):value(_value){}
};

好了,現(xiàn)在我們要通過(guò)類型系統(tǒng)來(lái)實(shí)現(xiàn)幾個(gè)操作的約束。對(duì)于乘除法我們要自動(dòng)計(jì)算出單位的同時(shí),加減法必須在相同的單位上才能做。其實(shí)這樣做還不夠完備,因?yàn)閷?duì)于任何的單位x來(lái)講,他們的差單位Δx還有一些額外的規(guī)則,就像C#的DateTime和TimeSpan一樣。不過(guò)這里先不管了,我們來(lái)做出加減乘除幾個(gè)操作:

template<int m, int s, int kg>
unit<m, s, kg> operator+(unit<m, s, kg> a, unit<m, s, kg> b)
{
    return a.value + b.value;
}

template<int m, int s, int kg>
unit<m, s, kg> operator-(unit<m, s, kg> a, unit<m, s, kg> b)
{
    return a.value - b.value;
}

template<int m, int s, int kg>
unit<m, s, kg> operator+(unit<m, s, kg> a)
{
    return a.value;
}

template<int m, int s, int kg>
unit<m, s, kg> operator-(unit<m, s, kg> a)
{
    return -a.value;
}

template<int m1, int s1, int kg1, int m2, int s2, int kg2>
unit<m1+m2, s1+s2, kg1+kg2>operator*(unit<m1, s1, kg1> a, unit<m2, s2, kg2> b)
{
    return a.value * b.value;
}

template<int m1, int s1, int kg1, int m2, int s2, int kg2>
unit<m1-m2, s1-s2, kg1-kg2>operator/(unit<m1, s1, kg1> a, unit<m2, s2, kg2> b)
{
    return a.value / b.value;
}

但是這個(gè)其實(shí)還不夠,我們還需要帶單位的值乘以或除以一個(gè)系數(shù)的代碼。為什么不能加減呢?因?yàn)椴煌瑔挝坏臇|西本來(lái)就不能加減。系數(shù)其實(shí)是可以描寫成unit<0, 0, 0>的,但是為了讓代碼更緊湊,于是多定義了下面的四個(gè)函數(shù):

template<int m, int s, int kg>
unit<m, s, kg> operator*(double v, unit<m, s, kg> a)
{
    return v * a.value;
}

template<int m, int s, int kg>
unit<m, s, kg> operator*(unit<m, s, kg> a, double v)
{
    return a.value * v;
}

template<int m, int s, int kg>
unit<m, s, kg> operator/(double v, unit<m, s, kg> a)
{
    return v / a.value;
}

template<int m, int s, int kg>
unit<m, s, kg> operator/(unit<m, s, kg> a, double v)
{
    return a.value / v;
}

我們已經(jīng)用dependent type之間的變化來(lái)描述了帶單位的量的加減乘除的規(guī)則。這看起來(lái)好像很復(fù)雜,但是一旦我們加入了下面的新的函數(shù),一切將變得簡(jiǎn)單明了:

constexpr unit<1, 0, 0> operator""_meter(double value)
{
    return value;
}

constexpr unit<0, 1, 0> operator""_second(double value)
{
    return value;
}

constexpr unit<0, 0, 1> operator""_kilogram(double value)
{
    return value;
}

constexpr unit<1, -2,1> operator""_N(double value) // 牛不知道怎么寫-_-
{
    return value;
}

constexpr unit<2, -2,1> operator""_J(double value) // 焦耳也不知道怎么寫-_-
{
    return value;
}

然后我們就可以用來(lái)寫一些神奇的代碼了:

auto m = 16_kilogram; // unit<0, 0, 1>(16)
auto s = 3_meter; // unit<1, 0, 0>(3)
auto t = 2_second; // unit<0, 1, 0>(2)
auto a = s / (t*t); // unit<1, -2, 0>(3/4)
auto F = m * a; // unit<1, -2, 1>(12)

下面的代碼雖然也神奇,但因?yàn)檫`反了物理定律,所以C++編譯器決定不讓他編譯通過(guò):

auto W = F * s; // unit<2, -2, 1>(36)
auto x = F + W; // bang!

這樣你還怕你在物理引擎里面東西倒騰來(lái)倒騰去然后公式手抖寫錯(cuò)了嗎?類似的錯(cuò)誤是不可能發(fā)生的!除非系數(shù)被你弄錯(cuò)了……如果沒(méi)有unit,要用原始的方法寫出來(lái):

double m = 16;
double s = 3;
double t = 2;
double a = s / (t*t);
double F = m * a;
double W = F * s;
double x = F + W; //????

時(shí)間過(guò)得久了以后,根本不知道是什么意思了。所以為了解決這個(gè)問(wèn)題,我們得用應(yīng)用匈牙利命名法(這個(gè)不是那個(gè)臭名昭著的你們熟悉的傻逼(系統(tǒng))匈牙利命名法)。我舉個(gè)例子:

string dogName = "kula";
Person person;
person.name = dogName;

這個(gè)代碼大家一看就知道不對(duì)對(duì)吧,這就是應(yīng)用匈牙利命名法了。我們通過(guò)給名字一個(gè)單位——狗的——來(lái)讓person.name = dogName;這句話顯得很滑稽,從而避免低級(jí)錯(cuò)誤的發(fā)生。上面的unit就更進(jìn)一步了,把這個(gè)東西帶進(jìn)了類型系統(tǒng)里面,就算寫出來(lái)不滑稽,編譯器都會(huì)告訴你,錯(cuò)誤的東西就是錯(cuò)誤的。

然后大家可能會(huì)問(wèn),用unit這么寫程序的性能會(huì)不會(huì)大打折扣呀?如今已經(jīng)是2013年了,靠譜的C++編譯器編譯出來(lái)的代碼,跟你直接用幾個(gè)double倒騰來(lái)倒騰去的代碼其實(shí)是一樣的。C++比起其他語(yǔ)言的抽象的好處就是,就算你要用來(lái)做高性能的程序,也不怕因?yàn)槌橄蠖鴨适阅堋.?dāng)然如果你使用了面向?qū)ο蟮募夹g(shù),那就另當(dāng)別論了。

注,上面這段話我寫完之后貼到了粉絲群里面,然后九姑娘跟我講了很多量綱分析的故事,然后升級(jí)到航空領(lǐng)域的check list,最后講到了醫(yī)院把這一技術(shù)引進(jìn)了之后有效地阻止了手術(shù)弄錯(cuò)人等嚴(yán)重事故。那些特別靠譜的程序還得用C++來(lái)寫,譬如說(shuō)洛克希德馬丁的戰(zhàn)斗機(jī),NASA的衛(wèi)星什么的。

人的精力是有限的,需要一些錯(cuò)誤規(guī)避來(lái)防止引進(jìn)低級(jí)的錯(cuò)誤或者負(fù)擔(dān),保留精力解決最核心的問(wèn)題。很多軟件都是這樣的。譬如說(shuō)超容易配置的MSBuild、用起來(lái)巨爽無(wú)比的Visual Studio,出了問(wèn)題反正用正版安裝程序點(diǎn)一下repair就可以恢復(fù)的windows,給我們帶來(lái)的好處就是——保留精力解決最核心的問(wèn)題。編程語(yǔ)言也是如此,類型系統(tǒng)也是如此,人類發(fā)明出的所有東西,都是為了讓你可以把更多的精力放在更加核心的問(wèn)題上,更少的精力放在周邊的問(wèn)題上。

但是類型到處都出現(xiàn)其實(shí)也會(huì)讓我們程序?qū)懫饋?lái)很煩的,所以現(xiàn)代的語(yǔ)言都有第二個(gè)功能,就是類型推導(dǎo)了。

二、類型推導(dǎo)

這里講的類型推導(dǎo)可不是Go語(yǔ)言那個(gè)半吊子的:=賦值操作符。真正的類型推導(dǎo),就要跟C++的泛型lambda表達(dá)式、C#的linq語(yǔ)法糖,或者Haskell的函數(shù)一樣,要可以自己計(jì)算出模板的類型參數(shù)的位置或者內(nèi)容,才能全方位的實(shí)現(xiàn)什么類型都不寫,都還能使用強(qiáng)類型和type rich programming帶來(lái)的好處。C++的lambda表達(dá)式上面已經(jīng)看到了,所以還是從Haskell一個(gè)老掉牙的demo開始講起吧。

今天,我們用Haskell來(lái)寫一個(gè)merge sort:

merge [] [] = []
merge [] xs = xs
merge xs [] = xs
merge (x:xs) (y:ys) = if x<y then x:(merge xs (y:ys)) else y:(merge (x:xs) ys)

mergeSort [] = []
mergeSort xs = merge (mergeSort a) (mergeSort b)
    where
        len = length xs
        a = take $ len `div` 2 $ xs
        b = drop $ len - len `div` 2 $ xs

我們可以很清楚的看出來(lái),merge的類型是[a] –> [a] –> [a],mergeSort的類型是[a] –> [a]。到底編譯器是怎么計(jì)算出類型的呢?

  1. 首先,[]告訴我們,這是一個(gè)空列表,但是類型是什么不知道,所以他是forall a –> [a]。所以merge [] [] = []告訴我們,merge的類型至少是[a] –> [b] –> [c]。
  2. 其次,merge []  xs = xs告訴我們,merge的類型至少是[d] –> e –> e。這個(gè)類型跟[a]->[b]->[c]求一個(gè)交集就會(huì)得到merge的更準(zhǔn)確的類型:[a] –> [b] –> [b]。
  3. 然后,merge xs [] = []告訴我們,merge的類型至少是f –> [g] –> f。這個(gè)類型跟[a] –> [b] –> [b]求一個(gè)交集就會(huì)得到merge的更準(zhǔn)確的類型:[a] –> [a] –> [a]。
  4. 最后看到那個(gè)長(zhǎng)長(zhǎng)的式子,根據(jù)一番推導(dǎo)之后,會(huì)發(fā)現(xiàn)[a]->[a]->[a]就是我們要的最終類型了。
  5. 只要把相同的技術(shù)放在mergeSort上面,就可以得到mergeSort的類型是[a]->[a]了。

當(dāng)然對(duì)于Haskell這種Hindley-Milner類型系統(tǒng)來(lái)說(shuō),只要我們?cè)诖a里面計(jì)算出所有類型的方程,然后一個(gè)一個(gè)去解,最后就可以收斂到一個(gè)最準(zhǔn)確的類型上面了。倘若我們?cè)诘臅r(shí)候發(fā)現(xiàn)收斂之后無(wú)解了,那這個(gè)程序就是錯(cuò)的。這種簡(jiǎn)單粗暴的方法很容易構(gòu)造出一些只要人夠蛋定就很容易使用的語(yǔ)言,譬如說(shuō)Haskell。

Haskell看完就可以來(lái)看C#了。C#的linq真是個(gè)好東西啊,只要不把它看成SQL,那很多事情都可以表達(dá)的。譬如說(shuō)是個(gè)人都知道的linq to object啦,后面還有linq to xmllinq to sqlreactive programming,甚至是parser combinator等等。一個(gè)典型的linq的程序是長(zhǎng)這個(gè)樣子的:

var w = 
    from x in xs
    from y in ys
    from z in zs
    select f(x, y, z);

 

光看這個(gè)程序可能看不出什么來(lái),因?yàn)閤s、ys、zs和f這幾個(gè)單詞都是沒(méi)有意義的。但是linq的魅力正在這里。如果from和select就已經(jīng)強(qiáng)行規(guī)定了xs、ys、zs和f的意思的話。那可擴(kuò)展性就全沒(méi)有了。因此當(dāng)我們看到一個(gè)這樣的程序的時(shí)候,其實(shí)可以是下面這幾種意思:

W f(X x, Y y, Z z);

var /*IEnumerable<W>*/w = 
    from /*X*/x in /*IEnumerable<X>*/xs
    from /*Y*/y in /*IEnumerable<Y>*/ys
    from /*Z*/z in /*IEnumerable<Z>*/zs
    select f(x, y, z);

var /*IObservable<W>*/w = 
    from /*X*/x in /*IObservable<X>*/xs
    from /*Y*/y in /*IObservable<Y>*/ys
    from /*Z*/z in /*IObservable<Z>*/zs
    select f(x, y, z);

var /*IParser<W>*/w = 
    from /*X*/x in /*IParser<X>*/xs
    from /*Y*/y in /*IParser<Y>*/ys
    from /*Z*/z in /*IParser<Z>*/zs
    select f(x, y, z);
var /*IQueryable<W>*/w =
from /*X*/x in /*IQueryable<X>*/xs
from /*Y*/y in /*IQueryable<Y>*/ys
from /*Z*/z in /*IQueryable<Z>*/zs
select f(x, y, z);
var /*?<W>*/w = 
    from /*X*/x in /*?<X>*/xs
    from /*Y*/y in /*?<Y>*/ys
    from /*Z*/z in /*?<Z>*/zs
    select f(x, y, z);

 

相信大家已經(jīng)看到了里面的pattern了。只要你有一個(gè)?<T>類型,而它又支持linq provider的話,你就可以把代碼寫成這樣了。

不過(guò)我們知道,把程序?qū)懗蛇@樣并不是我們編程的目的,我們的目的是要讓程序?qū)懙米尵哂型瑯又R(shí)背景的人可以很快就看懂。為什么要看懂?因?yàn)榭傆幸惶炷銜?huì)不維護(hù)這個(gè)程序的,這樣就可以讓另一個(gè)合格的人來(lái)繼續(xù)維護(hù)了。一個(gè)軟件是要做幾十年的,那些只有一兩年甚至只有半年生命周期的,只能叫垃圾了。

那現(xiàn)在讓我們看一組有意義的linq代碼。首先是linq to object的,求一個(gè)數(shù)組里面出現(xiàn)最多的數(shù)字是哪個(gè):

var theNumber = (
    from n in numbers
    group by n into g
    select g.ToArray() into gs
    order by gs.Length descending
    select gs[0]
    ).First()

 

其次是一個(gè)parser,這個(gè)parser用來(lái)得到一個(gè)函數(shù)調(diào)用表達(dá)式:

IParser<FunctionCallExpression> Call()
{
    return
        from name in PrimitiveExpression()
        from _1 in Text("(")
        from arguments in
            many(
                Expression().
                Text(",")
            )
        from _2 in Text(")")
        select new FunctionCallExpression
        {
            Name = name,
            Arguments = arguments.ToArray(),
        };
}

 

我們可以看到,一旦linq表達(dá)式里面的元素都有了自己的名字,就不會(huì)跟上面的xyz的例子一樣莫名其妙了。那這兩個(gè)例子到底為什么要用linq呢?

第一個(gè)例子很簡(jiǎn)單,因?yàn)閘inq to object就是設(shè)計(jì)來(lái)解決這種問(wèn)題的。

第二個(gè)例子就比較復(fù)雜一點(diǎn)了,為什么好好地parser要寫成這樣呢?我們知道,parser時(shí)可能會(huì)parse失敗的。一個(gè)大的parser,里面的一些小部件失敗了,那么大parser就要回滾,token stream的當(dāng)前光標(biāo)也要回滾,等等需要類似的一系列的操作。如果我們始終都讓這些邏輯都貫穿在整個(gè)parser里面,那代碼根本不能看。于是我們可以寫一個(gè)linq provider,讓SelectMany函數(shù)來(lái)處理所有的回滾操作,然后把parser寫成上面這個(gè)樣子。上面這個(gè)parser的所有的in左邊是臨時(shí)變量,所有的in右邊剛好組成了一個(gè)EBNF文法:

PrimitiveExpression "(" [Expression {"," Expression}] ")"

 

最后的select語(yǔ)句告訴我們?cè)谒行arser都parse成功之后,如何用parse得到的臨時(shí)變量來(lái)返回一顆語(yǔ)法樹。整個(gè)parsing的代碼就會(huì)非常的容易看懂。當(dāng)然,前提是你必須要懂的EBNF。不過(guò)一個(gè)不懂EBNF的人,又如何能寫語(yǔ)法分析器呢。

那這跟類型推導(dǎo)有什么關(guān)系呢?我們會(huì)發(fā)現(xiàn)上面的所有l(wèi)inq的例子里面,除了函數(shù)簽名以外,根本沒(méi)有出現(xiàn)任何類型的聲明。而且更重要的是,這些類型盡管沒(méi)有寫出來(lái),但是每一個(gè)中間變量的類型都是自明的。當(dāng)然這有一部分歸功于好的命名方法——也就是應(yīng)用匈牙利命名法了。剩下的部分是跟業(yè)務(wù)邏輯相關(guān)的。譬如說(shuō),一個(gè)FunctionCallExpression所調(diào)用的函數(shù)當(dāng)然也是一個(gè)Expression了。如果這是唯一的選擇,那為什么要寫出來(lái)呢?

我們可以看到,正是因?yàn)橛辛祟愋屯茖?dǎo),我們可以在寫出清晰的代碼的同時(shí),還不需要花費(fèi)那么多廢話來(lái)指定各種類型。程序員都是怕麻煩的,無(wú)論復(fù)雜的方法有多好,他總是會(huì)選擇簡(jiǎn)單的(廢話,用復(fù)雜的那個(gè)不僅要加班修bug,還沒(méi)有漲工資。用簡(jiǎn)單的那個(gè),先讓他過(guò)了,bug留給下一任程序員去頭疼就好了——某web程序員如是說(shuō))。類型推導(dǎo)讓type rich programming的程序?qū)懫饋?lái)簡(jiǎn)單了許多。所以我們一旦有了類型推導(dǎo),就可以放心大膽的使用type rich programming了。

三、大道理

有了type rich programming,就可以讓編譯器幫我們檢查一些模式化的手都會(huì)犯的錯(cuò)誤。讓我們重溫一下這篇文章前面的一段話:

人的精力是有限的,需要一些錯(cuò)誤規(guī)避來(lái)防止引進(jìn)低級(jí)的錯(cuò)誤或者負(fù)擔(dān),保留精力解決最核心的問(wèn)題。很多軟件都是這樣的。譬如說(shuō)超容易配置的MSBuild、用起來(lái)巨爽無(wú)比的Visual Studio,出了問(wèn)題反正用正版安裝程序點(diǎn)一下repair就可以恢復(fù)的windows,給我們帶來(lái)的好處就是——保留精力解決最核心的問(wèn)題。編程語(yǔ)言也是如此,類型系統(tǒng)也是如此,人類發(fā)明出的所有東西,都是為了讓你可以把更多的精力放在更加核心的問(wèn)題上,更少的精力放在周邊的問(wèn)題上。

這讓我想起了一個(gè)在微博上看到的故事:NASA的員工在推一輛裝了衛(wèi)星的小車的時(shí)候,因?yàn)橥浛碿heck list,沒(méi)有固定號(hào)衛(wèi)星,結(jié)果衛(wèi)星一推倒在了地上摔壞了,一下子沒(méi)了兩個(gè)億的美元。

寫程序也一樣。一個(gè)代表力的變量,只能跟另一個(gè)代表力的變量相加,這就是check list。但是我們知道,每一個(gè)程序都相當(dāng)復(fù)雜,check list需要檢查的地方遍布所有文件。那難道我們?cè)赾ode review的時(shí)候可以一行一行仔細(xì)看嗎?這是不可能的。正因?yàn)槿绱耍覀円殉绦驅(qū)懗?#8220;讓編譯器可以檢查很多我們可能會(huì)手抖犯的錯(cuò)誤”的形式,讓我們從這些瑣碎的事情里面解放出來(lái)。

銀彈這種東西是不存在的,所以type rich programming能解決的事情就是防止手抖而犯錯(cuò)誤。有一些錯(cuò)誤不是手抖可以抖出來(lái)的,譬如說(shuō)錯(cuò)誤的設(shè)計(jì),這并不是type rich programming能很好地處理的范圍。為了解決這些事情,我們就需要更多可以遵守的best practice了。

當(dāng)然,其中的一個(gè)將是DSL——domain specific language,領(lǐng)域特定語(yǔ)言了。敬請(qǐng)關(guān)注下一篇,《如何設(shè)計(jì)一門語(yǔ)言(十)——DSL與建模》。

posted on 2013-08-17 00:26 陳梓瀚(vczh) 閱讀(12960) 評(píng)論(16)  編輯 收藏 引用 所屬分類: 啟示

評(píng)論:
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型[未登錄](méi) 2013-08-17 00:33 | me
大贊!  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-17 18:40 | DiryBoy
跪拜~~  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-23 01:12 | 42
比較好奇,你一直覺(jué)得ruby是個(gè)好語(yǔ)言,那python呢?這個(gè)和ruby比起來(lái)在支持DSL上是不是有什么缺陷  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-24 00:41 | resty
你覺(jué)得因?yàn)?quot;1"的類型是string所以"1"+2就應(yīng)該是"12"?啐!"1"的類型是(string | number),這才是正確的做法。

這個(gè)不是反了么,這里如果"1"是(string | number) "1" + 2應(yīng)該是3啊
目測(cè)這里應(yīng)該是2是(string | number)吧  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-24 05:51 | 陳梓瀚(vczh)
@42
你什么時(shí)候見過(guò)python里面有靠譜的DSL……  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-24 05:51 | 陳梓瀚(vczh)
@resty
沒(méi)錯(cuò)呀,我是支持"1"+2==3的、  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-24 21:16 | resty
@陳梓瀚(vczh)
我是看你原文寫"1"+2=="12"以為是想說(shuō)這個(gè)。

說(shuō)起來(lái)我覺(jué)得這個(gè)類型應(yīng)該叫(string & number),是同時(shí)是而不是取其中一個(gè)的感覺(jué)  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-25 03:57 | AmaF
單從語(yǔ)言本身來(lái)說(shuō),我更喜歡js這種完全無(wú)節(jié)操的……所謂程序無(wú)非就是輸入和輸出,對(duì)于類型來(lái)說(shuō),輸出這一端是不需要檢查的,只有輸入需要檢查,js的話完全可以手動(dòng)進(jìn)行;而相對(duì)的,涉及到“泛型”的時(shí)候,js的表現(xiàn)就好太多了。  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-25 19:04 | 陳梓瀚(vczh)
@AmaF
那你“完全手動(dòng)進(jìn)行”了嗎?  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型[未登錄](méi) 2013-08-26 05:38 | zhaoyg
"C++里面所有中括號(hào)不寫東西的lambda表達(dá)式都可以被轉(zhuǎn)成一個(gè)函數(shù)指針的"
學(xué)習(xí)了,之前一直以為lambda是用類來(lái)實(shí)現(xiàn)的,原來(lái)還有另一種情況啊。  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-26 08:03 | AmaF
@陳梓瀚(vczh)
你是想說(shuō)檢查這件事本身對(duì)于程序員就很浪費(fèi)時(shí)間么?我覺(jué)得這就跟用c++的時(shí)候考慮內(nèi)存管理一樣是很正常的一種行為。
我現(xiàn)在搞v8的嵌入,目前主要用c++寫代碼,js輸入進(jìn)來(lái)的參數(shù)全都是專門寫代碼判斷類型,其實(shí)花不了多少時(shí)間。因?yàn)橹辉谳斎氲臅r(shí)候檢查,比起內(nèi)存管理兩頭顧可是輕松許多。
目前js開發(fā)沒(méi)什么好的IDE,debug甚至發(fā)現(xiàn)bug都很困難,我覺(jué)得這些更應(yīng)該歸咎于環(huán)境而不是語(yǔ)言本身上面。  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-26 08:58 | 陳梓瀚(vczh)
@AmaF
針對(duì)你這種情況,我認(rèn)為你可以使用Visual Studio和TypeScript。TypeScript是js的擴(kuò)展,他可以被編譯成js。TypeScript是強(qiáng)類型的,但是編譯后的js也不會(huì)去檢查,所有的檢查只在編譯的時(shí)候做。很多著名的類庫(kù)譬如jquery已經(jīng)有TypeScript的類型簽名了,所以使用起來(lái)很方便。

如果你喜歡的話還可以用FunScript,是一個(gè)利用庫(kù)的TypeScript簽名來(lái)做強(qiáng)類型的類庫(kù),然后把F#編譯成js的東西。

以上兩個(gè)都不需要你親自檢查,也不會(huì)有運(yùn)行時(shí)的開銷,VS還可以幫你調(diào)試,用起來(lái)超方便。  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-08-26 20:40 | AmaF
@陳梓瀚(vczh)
謝謝~ TypeScript看起來(lái)不錯(cuò),f#就算了,那語(yǔ)法看著頭疼。
不過(guò)我其實(shí)做的是類似node.js那樣的工作,到時(shí)候可以把這些東西提供給用戶……  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-09-15 18:27 | shaoyuan1943
是12,“1” + 2,1沒(méi)錯(cuò),2 才是string,@resty
  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-09-20 18:55 | 大島小柚子
lambda表達(dá)式在C++11就有了,不必等到C++14。
在vs2010也可以輕松使用。  回復(fù)  更多評(píng)論
  
# re: 如何設(shè)計(jì)一門語(yǔ)言(九)&mdash;&mdash;類型 2013-09-23 18:53 | 陳梓瀚(vczh)
@大島小柚子
VS2010的lambda的雙重this capture有bug,VS2012的lambda還不支持auto類型參數(shù)還得寫得老長(zhǎng)  回復(fù)  更多評(píng)論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            久久精品国产精品| 久久亚洲精品一区二区| 亚洲天堂网在线观看| 一区二区三区产品免费精品久久75| 91久久黄色| 99pao成人国产永久免费视频| 亚洲精品国偷自产在线99热| 日韩一区二区福利| 亚洲一区二三| 亚洲欧美资源在线| 久久精品国产91精品亚洲| 久久婷婷一区| 欧美成人在线网站| 亚洲伦理精品| 亚洲一区www| 欧美中文字幕久久| 免费视频一区| 欧美三级视频在线| 国产精品一区一区三区| 黄网站色欧美视频| 亚洲人成人一区二区三区| 亚洲视频在线一区| 久久av在线| 欧美福利影院| 一本一本久久a久久精品综合麻豆 一本一本久久a久久精品牛牛影视 | 亚洲日本久久| 亚洲丝袜av一区| 久久丁香综合五月国产三级网站| 欧美在线观看视频| 欧美va日韩va| 99综合电影在线视频| 午夜在线成人av| 欧美成人影音| 国产精品久久久久国产a级| 激情久久一区| 一区二区成人精品| 久久免费视频一区| 日韩亚洲欧美在线观看| 午夜国产精品视频| 欧美电影免费观看大全| 国产精品欧美日韩一区| 亚洲国产精品99久久久久久久久| 中文一区在线| 免费一级欧美片在线观看| 亚洲最新在线| 久久久久久一区二区| 欧美日韩一区二区在线视频| 黄色日韩网站| 中国成人在线视频| 欧美freesex8一10精品| 亚洲一区二区高清视频| 欧美+亚洲+精品+三区| 国产欧美日韩亚洲一区二区三区| 日韩视频在线永久播放| 久久久久中文| 一区二区三区波多野结衣在线观看| 久久九九有精品国产23| 国产精品99一区二区| 亚洲国产成人久久| 久久精品一区| 一片黄亚洲嫩模| 欧美jizz19性欧美| 国内成人精品一区| 亚洲欧美综合网| 最新亚洲电影| 另类酷文…触手系列精品集v1小说| 国产精品欧美激情| 一区二区三区**美女毛片| 欧美成人精品三级在线观看| 午夜精品久久久久| 欧美性猛交xxxx免费看久久久 | 在线视频欧美精品| 欧美激情国产日韩精品一区18| 亚洲欧美日韩精品在线| 欧美日韩另类丝袜其他| 亚洲日韩欧美视频一区| 免费成人在线视频网站| 欧美在线播放| 国产拍揄自揄精品视频麻豆| 亚洲一区精彩视频| 99热在这里有精品免费| 欧美精品一区二区三区蜜桃 | 国产日韩久久| 亚洲女人小视频在线观看| 亚洲精品日韩一| 欧美韩日一区二区| 亚洲人成网站精品片在线观看| 狂野欧美激情性xxxx| 久久国产精品久久久久久| 国产午夜精品在线观看| 久久av在线| 午夜一区二区三区不卡视频| 国产精品永久免费| 午夜免费在线观看精品视频| 亚洲午夜在线观看| 国产精品久久夜| 午夜精品视频网站| 亚洲一区二区av电影| 国产精品一香蕉国产线看观看 | 国产一区二区三区四区五区美女 | 亚洲一区二区三区中文字幕在线| 欧美日韩精品在线播放| 一本色道精品久久一区二区三区 | 亚洲成色www久久网站| 欧美成人精品激情在线观看| 美女诱惑一区| 亚洲精品日韩在线| 亚洲开发第一视频在线播放| 欧美三区在线| 性欧美videos另类喷潮| 欧美一区二区三区另类 | 久久精品国产96久久久香蕉| 午夜精品视频在线| 狠狠狠色丁香婷婷综合久久五月 | 国产午夜精品视频免费不卡69堂| 久久精品成人| 久久在线免费观看视频| 亚洲精品国产欧美| 一本久久a久久免费精品不卡| 国产精品丝袜91| 久久久久久夜| 欧美大片免费看| 亚洲夜晚福利在线观看| 午夜精品亚洲一区二区三区嫩草| 韩国成人福利片在线播放| 欧美成人综合在线| 欧美日韩中文另类| 欧美在线在线| 免费在线视频一区| 亚洲无线观看| 久久精品91久久香蕉加勒比 | 国产一区二区0| 欧美大胆人体视频| 欧美性猛交99久久久久99按摩| 欧美中日韩免费视频| 美女福利精品视频| 亚洲欧美日韩在线不卡| 久久久久久久久久久久久久一区| 亚洲裸体在线观看| 午夜精品www| 亚洲精品网址在线观看| 亚洲一区亚洲| 亚洲清纯自拍| 亚洲欧美日韩综合| 亚洲久久视频| 午夜视频在线观看一区二区| 亚洲欧洲精品一区| 午夜精品久久久久久久久| 亚洲青涩在线| 欧美伊人久久久久久久久影院| 日韩特黄影片| 久久激情中文| 亚洲欧美视频一区二区三区| 免费观看成人鲁鲁鲁鲁鲁视频| 亚洲欧美中文日韩v在线观看| 媚黑女一区二区| 欧美一级专区免费大片| 欧美国产精品v| 久久噜噜噜精品国产亚洲综合| 欧美日韩国产成人在线免费| 久久亚洲综合色一区二区三区| 欧美日韩免费观看中文| 免费观看成人www动漫视频| 国产精品伦理| 亚洲精品视频在线| 亚洲第一在线| 午夜精品美女自拍福到在线 | 一区二区在线观看视频在线观看| 日韩视频亚洲视频| 亚洲国产日日夜夜| 欧美在线|欧美| 午夜日韩福利| 欧美日韩在线直播| 亚洲国产欧美另类丝袜| 国产综合色产在线精品| 亚洲图中文字幕| 一本久久综合亚洲鲁鲁| 欧美成人免费va影院高清| 久久在线免费观看| 国产小视频国产精品| 亚洲伊人伊色伊影伊综合网| 99亚洲精品| 欧美国产一区二区三区激情无套| 久久综合网络一区二区| 国产性天天综合网| 亚洲一区二区在线免费观看视频| 在线一区二区视频| 欧美激情视频一区二区三区在线播放| 毛片基地黄久久久久久天堂| 国产综合亚洲精品一区二| 午夜一级久久| 欧美在线免费一级片| 国产精品试看| 亚洲欧美美女| 久久成人免费视频| 国产视频一区在线观看| 亚洲欧美日韩中文视频| 久久se精品一区精品二区| 国产伦精品一区二区三区免费迷| 亚洲一区二区三区高清不卡|