28

哈斯克爾,我可能會實現if這樣的:伊德里斯急於評價

if' True x y = x 
if' False x y = y 
spin 0 =() 
spin n = spin (n - 1) 

行爲我怎麼想到

haskell> if' True (spin 1000000)() -- takes a moment 
haskell> if' False (spin 1000000)() -- immediate 

球拍,我可以實施一個有缺陷的if像這樣:

(define (if2 cond x y) (if cond x y)) 
(define (spin n) (if (= n 0) (void) (spin (- n 1)))) 

行爲我怎麼想到

racket> (if2 #t (spin 100000000) (void)) -- takes a moment 
racket> (if2 #f (spin 100000000) (void)) -- takes a moment 

伊德里斯,我可能會實現if這樣的:

if' : Bool -> a -> a -> a 
if' True x y = x 
if' False x y = y 
spin : Nat ->() 
spin Z =() 
spin (S n) = spin n 

行爲surpris上課我

idris> if' True (spin 1000)() -- takes a moment 
idris> if' False (spin 1000)() -- immediate 

我預計Irdis表現得像個球拍,其中兩個參數是 評估。但事實並非如此!

伊德里斯如何決定何時評估事物?

回答

40

我們說伊德里斯有嚴格的評估,但這是針對它的運行時語義。

作爲完全依賴類型的語言,Idris有兩個階段,它評估事物,編譯時和運行時。在編譯時,它只會評估它知道的全部內容(即終止並覆蓋所有可能的輸入),以便確保類型檢查是可確定的。編譯時評估器是Idris內核的一部分,並且在Haskell中使用HOAS(高階抽象語法)樣式表示值來實現。由於這裏所有東西都有一個正常的形式,所以評估策略實際上並不重要,因爲無論哪種方式它都會得到相同的答案,並且在實踐中,它將執行Haskell運行時系統選擇執行的任何操作。

爲方便起見,REPL使用編譯時評估的概念。所以,你的'旋轉1000'實際上從來沒有得到評估。如果您使用相同的代碼創建可執行文件,我期望看到非常不同的行爲。

除了更容易實現(因爲我們有評估器可用),這可以非常有用地顯示術語如何在類型檢查器中評估。所以你可以看到的區別:

Idris> \n, m => (S n) + m 
\n => \m => S (plus n m) : Nat -> Nat -> Nat 

Idris> \n, m => n + (S m) 
\n => \m => plus n (S m) : Nat -> Nat -> Nat 

這將是困難(雖然不是不可能的),如果我們使用的REPL,這下不lambda表達式減少運行時的評價。

+0

謝謝!我不得不將'spin:Nat - >()'改爲'spin:Nat - > Bool'(我猜Idris意識到'()'只有一個居民,並且優化了對spin的調用),但是在無論「if」分支是哪個分支,可執行文件都花了一些時間運行。 – Snowball

+0

是的,它會清除()。實際上,在當前的主控制器中,它執行了更深的擦除分析,因此您可能必須執行一些操作,比如打印旋轉n的結果以確保評估結果... –

+1

如果知道「旋轉1000」是終止並且沒有副作用,這個表達式不會在編譯時被評估,而是被替換爲生成的代碼中的結果,所以這兩個版本的'if''-line會在運行時立即返回它們的結果? (上面的雪球註釋表明它沒有。) – Lii