PLT:說說Evaluation strategy

Brief                              

在學習方法/函數時,咱們總會接觸到 按值傳值 和 引用傳值 兩個概念。像C#是按值傳值,但參數列表添加了ref/out後則是引用傳值,但奇怪的事出現了html

namespace Foo{
  class Bar{
    public String Msg{get;set;}
  }
  class Program{
    public static void main(String[] args){
      Bar bar1 = new Bar();
      bar1.Msg = "Hey, man!";
      UpdateProp(bar1);
      Console.WriteLine(bar1.Msg); // Bye!
    }
    static void UpdateProp(Bar bar){
      bar.Msg = "Bye!";
    }
  }
}

Q:UpdateProp明明是按值傳值,對bar的修改怎麼會影響到main中的bar1呢?
延伸Q:到底什麼是按值傳值、引用傳值?
爲了解答上述疑問,咱們就須要理解求值策略了!算法

What is evaluation strategy?                

Evaluation Strategy其實就是定義函數/方法的實參應該在什麼時候被計算,而且以什麼類型的值做爲實參傳入到函數/方法內部。編程

A programming language uses an evaluation strategy to determine when to evaluate the argument(s) of a function call (for function, also read: operation, method, or relation) and what kind of value to pass to the function.promise

以時間爲維度,那麼就有如下三種類別的求值策略:
1. Strict/Eager Evaluation,在執行函數前對實參求值(實質上是在構建函數執行上下文前)。
2. Non-strict Evaluation(Lazy Evaluation),在執行函數時纔對實參求值。
3. Non-deterministic,實參求值時機飄忽。
另外注意的是,大部分編程語言採用不止一種求值策略。併發

Strict/Eager evaluation                    

如今絕大部分語言均支持這類求值策略,而咱們也習覺得常,所以當如Linq、Lambda Expression等延遲計算出現時咱們才如此興奮不已。
但Strict/Eager Evaluation下還包括不少具體的定義,下面咱們來逐個瞭解。ecmascript

Applicative Order (Evaluation)

Applicative Order又名leftmost innermost,中文翻譯爲「應用序列」,實際運算過程和Post-order樹遍歷算法相似,必須先計算完葉子節點再計算根節點,所以下面示例將致使在計算實參時就發生內存溢出的bug。異步

// function definitions
function foo(){
  return false || foo()
}
function test(a, f){
  console.log(a + f)
}
// main thread,陷入foo函數無盡的遞歸調用中
test(1, foo())

Call-by-value

按值傳值也就是咱們接觸最多的一種求值策略,實際運算過程是對實參進行克隆,而後將副本賦值到參數變量中。async

function foo(val){
  val = 3
}
var bar = 1
foo(bar)
console.log(bar) // 顯示1
// 函數做用域中對實參進行賦值操做,並不會影響全局做用域的變量bar的值。

那如Brief中C#那種狀況究竟是啥回事呢?其實問題在於 到底要克隆哪裏的「值」了,對於Bar bar = new Bar()而言,bar對應的內存空間存放的是指向 new Bar()內存空間的指針,而所以克隆的就是指針而不是 new Bar()這個對象,也就是說克隆的是實參對應的內存空間存放的「值」。假如咱們將Bar定義爲Struct而不是Class,則明白C#確實遵循Call-by-value策略。編程語言

namespace Foo{
  struct Bar{
    public String Msg{get;set;}
  }
  class Program{
    public static void main(String[] args){
      Bar bar1 = new Bar();
      bar1.Msg = "Hey, man!";
      UpdateProp(bar1);
      Console.WriteLine(bar1.Msg); // Hey,man!
    }
    static void UpdateProp(Bar bar){
      bar.Msg = "Bye!";
    }
  }
}

稍微總結一下,Call-by-value有以下特色:
1. 若克隆的「值」爲值類型則爲值自己,而且在函數內的任何修改將不影響外部對應變量的值;
2. 若克隆的「值」爲引用類型則爲內存地址,而且在函數內的修改將影響外部對應變量的值,但賦值操做則不影響外部對應變量的值。
注意:因爲第2個特色與Call-by-sharing的特色是同樣的,所以雖然Java應該屬於採用Call-by-sharing策略,但社區仍是聲稱Java採用的是Call-by-value策略。ide

Call/Pass-by-reference

其實Call-by-reference和Call-by-value同樣那麼容易被人誤會,覺得把內存地址做爲實參傳遞就是Call-by-reference,其實否則。關鍵是這個「內存地址」是實參對應的內存地址,而不是實參對應的內存中所存放的地址。C語言木有自然地支持Call-by-reference策略,但能夠經過指針來模擬,反而能讓咱們更好地理解整個求值過程。

int i = 1;
int *pI = &i; // &i會獲取i對應內存空間的地址,並存放到pI對應的內存空間中

void foo(int *);
void foo(int *pI){
  pI = 2; // 直接操做i對應的內存空間,等同於i = 2
}
int main(){
  foo(pI);
  printf("%s", i); // 返回2
  return 0;
}

內存結構:
圖片描述
而C#可經過在形參上添加ref或out來設置採用Call-by-reference策略,Java和JavaScript就天生不支持也沒有提供模擬的方式。

Call-by-sharing/object/object-sharing

採用該策略的語言暗示該語言主要基於引用類型而不是值類型。

Call by sharing implies that values in the language are based on objects rather than primitive types, i.e. that all values are "boxed".

明顯Java和受Java影響甚深的JavaScript就是採用這種策略的。
該策略特色和Call-by-value的個特色一致。

Call-by-copy-restore(copy in copy out, call-by-value-result, call-by-value-return)

暫時我還沒接觸到哪一種語言採用了Call-by-copy-restore這種求值策略,它的運算過程主要分爲兩步:
1. 如Call-by-value的特色1那樣,對實參進行拷貝操做,而後將副本傳遞到函數體內。重點是,即便實參爲引用類型,也對引用所指向的對象進行拷貝,而不是僅拷貝指針而已。
效果:在函數體內對實參的任何操做(PutValue和Assignment)均不影響外部對應的變量。
2. 當退出函數執行上下文後,將實參值賦值到外部對應的變量。

/*** pseudo code ***/
var a = {}
function foo(a){
  a.name = 'fsjohnhuang'
  console.log('within foo:' + a.name)
  // 線程掛起1000ms
  var sd = +new Date()
  while(+new Date - sd < 1000);
}
// 異步執行foo
var promise = foo.async(a)
while(+new Date - sd < 100);

// 未退出foo的執行上下文時,訪問a.name,返回undefined
console.log(a.name)
if (promise.done){
  // 退出foo的執行上下文時,返回a.name,返回'fsjohnhuang'
  console.log(a.name)
}

Partial evaluation

便是部分實參在進入函數執行上下文前將不參與求值操做。示例以下:

var freeVar = {type: 'freeVar'}
function getName(){
  return freeVar
}
function print(msg, fn){
  console.log(msg + fn())
}

// 調用print時getName將不會被立刻求值
print('Hi,', getName)

能夠看到上述print函數調用時不會立刻對getName實參求值,但會立刻對'Hi,'進行求值操做。而須要注意的地方是,因爲getName是延遲計算,若函數體內存在自由變量(如freeVar),那麼後續的每次計算結果均有可能不一樣(也就是side effect)。

Non-strict evaluation(lazy-evaluation/calculation)    

Non-strict Evaluation是指在執行函數體的過程當中,須要用到該實參才進行運算的策略。還記得邏輯運算符(||,&&)的短路運算(short-circuit evaluation)嗎?這個就是延遲計算其中一個實例。
下面咱們一塊兒來了解4種延遲計算策略吧!

Normal Order (Evaluation)

Normal Order又名leftmost outermost,中文翻譯爲「正常序列」,通常經過與Applicative Order做對比來理解效果較好。還記得Applicative Order可能會引發內存溢出的問題嗎?那是由於Applicative Order會不斷地對AST中層數最深的可規約表達式節點優先求值的緣故,而Normal Order則採用計算完AST中層數最淺的可規約表達式節點便可。

/ function definitions
function foo(){
  return false || foo()
}
function test(a, f){
  console.log(a + f)
}
// main thread, 顯示 "1false"
test(1, foo())

Call-by-name

這種延遲計算策略十分容易明白,計算過程就是在執行函數體時,遇到需計算實參表達式時才執行運算。注意點:
1. 每次在執行實參表達式時均會執行運算;
2. 若實參的運算過程爲計算密集型或阻塞性操做時,則會阻塞函數體後續命令的執行。(這時會可經過Thunk對Call-by-name進行優化)

Call-by-need

其實就是Call-by-name + Memoized,就是第一計算實參表達式時,在返回計算結果的同時內部自動保存該結果,當下次執行實參表達式計算時直接返回首次計算的結果。注意點:
1. 該策略僅適用於pure function的實參,存在free variable則會致使沒法確保每次的求值結果都同樣。

Call-by-macro-expansion

在Clojure中使用macro時則就是採用Call-by-macro-expansion策略,會執行expansion階段對函數體內的實參表達式替換爲macro所定義的表達式,而後在進行運算。

Non-deterministic strategies                

另外因爲實參的運算時機具備不肯定性,所以下面的策略不能納入Strict和Non-strict求值策略中。

Call-by-future

這是一個併發求值策略,就是將求值操做委託給future,並由後續的promise去完成求值操做,而後調用者則經過future獲取求值結果。注意點:
1. 求值操做可能發生在future剛建立時,也有可能調用future獲取結果時才求值。

Conclusion

上述是查閱各資料後,對幾類求值策略的理解,如有紕漏請你們指正,謝謝!

Thanks                            

https://en.wikipedia.org/wiki/Evaluation_strategy
http://stackoverflow.com/questions/8848402/whats-the-difference-betwee...
https://en.wikipedia.org/wiki/Thunk#Call_by_name
http://blog.csdn.net/institute/article/details/23750307
http://www.cnblogs.com/leowanta/articles/2958581.html
http://blog.csdn.net/sk__________________/article/details/12848597
http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/
若是您以爲本文的內容有趣就掃一下吧!捐贈互勉!
  
圖片描述

相關文章
相關標籤/搜索