5

OCaml'ın belirli bir işlevi kuyruk özyineli olarak algılayıp tanımadığını nasıl anlarım? OCaml derleyici aşağıdaki Jeffrey cevabı Short-circuited operators and tail recursionBir OCaml işlevinin kuyruk özyinelemeli olduğunu doğrulayın


Teşekkür tanırsa Özellikle, öğrenmek istiyorum,

let rec check_all l = 
    match l with 
    | [] -> true 
    | hd :: tl -> 
     hd && check_all tl 

ve gerçekten de hiç optimize etmez basit işlevi ile bu çalıştı:

camlTest__check_all_1008: 
     .cfi_startproc 
.L102: 
     cmpl $1, %eax 
     je  .L100 
     movl (%eax), %ebx 
     cmpl $1, %ebx 
     je  .L101 
     movl 4(%eax), %eax 
     jmp  .L102 
     .align 16 
.L101: 
     movl $1, %eax 
     ret 
+0

: bunu nasıl güzel montaj çıktı aldın Ancak, bu fonksiyon şu anda bir hata var, ona şu yama uygulamak gerekir bunu düzeltmek için (artık destekleniyor gibi görünüyor)? – aneccodeal

+1

Jeffrey'nin aşağıdaki cevaba bakınız: ocamlopt -c-S – dspyz

cevap

4

OCaml 4.03'ten başlayarak ve Changes dosyasındaki yazım hatalarına rağmen, bir işlev uygulamasında @tailcall'u kullanabilir ve eğer durum böyle değilse derleyici uyarır. f x y bir kuyruk çağrı değilse

(f [@tailcall]) x y uyarıyor

Örnek: tüm aramalar için ek açıklamaları dökümü

$ cat tailrec.ml 
let rec f a x = if x <= 1 then a else (f [@tailcall]) (a * x) (x - 1) 

let rec g x = if x <= 1 then 1 else x * (g [@tailcall]) (x - 1) 

$ ocaml tailrec.ml 

File "tailrec.ml", line 3, characters 40-63: 
Warning 51: expected tailcall 
7

Diğer birçokları, OCaml dahili bilgisayarları hakkında bildiğim kadar akıllıdır, ancak basit işlevler için oluşturulan montaj kodunda kuyruk özlemini görmek oldukça kolaydır. camlopt: Eğer f için bu bakınız ekstra çıktı bir sürü yok sayarsanız

$ cat tailrec.ml 
let rec f a x = if x <= 1 then a else f (a * x) (x - 1) 

let rec g x = if x <= 1 then 1 else x * g (x - 1) 
$ ocamlopt -c -S tailrec.ml 

:

_camlTailrec__f_1008: 
     .cfi_startproc 
.L101: 
     cmpq $3, %rbx 
     jg  .L100 
     ret 
     .align 2 
.L100: 
     movq %rbx, %rdi 
     addq $-2, %rdi 
     sarq $1, %rbx 
     decq %rax 
     imulq %rbx, %rax 
     incq %rax 
     movq %rdi, %rbx 
     jmp  .L101 
     .cfi_endproc 

derleyici bir döngüye tekrarlanan aramalara değişti (yani fonksiyon kuyruk özyinelemelidir).

Burada g için ne olsun:

 .cfi_startproc 
     subq $8, %rsp 
     .cfi_adjust_cfa_offset 8 
.L103: 
     cmpq $3, %rax 
     jg  .L102 
     movq $3, %rax 
     addq $8, %rsp 
     .cfi_adjust_cfa_offset -8 
     ret 
     .cfi_adjust_cfa_offset 8 
     .align 2 
.L102: 
     movq %rax, 0(%rsp) 
     addq $-2, %rax 
     call _camlTailrec__g_1011 
.L104: 
     movq %rax, %rbx 
     sarq $1, %rbx 
     movq 0(%rsp), %rax 
     decq %rax 
     imulq %rbx, %rax 
     incq %rax 
     addq $8, %rsp 
     .cfi_adjust_cfa_offset -8 
     ret 
     .cfi_adjust_cfa_offset 8 
     .cfi_endproc 

tekrarlama gerçek özyinelemeli çağrı (kuyruk değil özyinelemeli) tarafından ele alınır.

Dediğim gibi, OCaml ara formları benden daha iyi anlarsanız, bunu anlamanın daha iyi yolları olabilir.

+0

İyi yanıt (+1). Kesin olmak gerekirse, kuyruk arama ve kuyruk arama optimizasyonu hakkında konuşulmamalıdır? OCaml'ın kuyruk çağrılarını optimize edip etmediğini (eğer ne anlama geliyorsa) değil, gerçekten sorun nedir? – Giorgio

+2

İşler daha karmaşık hale geldikçe, bilgi, derleyicilere '-annot 'seçeneğinin çıktısında da sağlanır; bu, tür açıklamalarının dosyayı kuyruğa çağrı bilgileriyle süsleyebildiğini gösteren bir araçtır. Ocamlopt [.opt] '' -dlinear '' seçeneği de, kuyruk aramaları ile değiştirilmiş kaynak çıkışında annotatoinlere sahip olacaktır. – nlucaroni

1

kimse saygıdeğer -annot seçeneği hakkında neden anlattığımı merak, . Montajın en emin yöntem olmasına rağmen, herkes meclisi okumakta iyi değildir. Ama annot ile o kadar kolay ki, onu otomatikleştirebilirsiniz. , ocaml -annot test.ml bir dosyayı derlemek bir test.annot dosya oluşturur

ocamlc -annot test.ml && if grep -A1 call test.annot | grep stack; then echo "non tailrecursive"; exit 1; fi 

o: Örneğin, kod test.ml dosyada olduğu göz önüne alındığında, otomatik olarak tüm çağrılar aşağıdaki tek satırlık yer ile kuyruk konumda olup olmadığını kontrol edebilirsiniz Her ifade için bir açıklama içerecektir. grep -A1 call test.annot, tüm arama ek açıklamalarını çıkarır ve içeriğine bakar. En az bir yığın çağrı varsa, grep stack doğru döner.

Aslında, bu bilgileri annot dosyasından ayıklayabileceğiniz, the ocaml deposunda bulabileceğiniz bir emacs tamamlayıcısı bile vardır. Örneğin, noktada belirtilen bir işlev çağrısı gösteren bir caml-types-show-call işlevi vardır.BTW

--- a/emacs/caml-types.el 
+++ b/emacs/caml-types.el 
@@ -221,7 +221,7 @@ See `caml-types-location-re' for annotation file format." 
       (right (caml-types-get-pos target-buf (elt node 1))) 
       (kind (cdr (assoc "call" (elt node 2))))) 
      (move-overlay caml-types-expr-ovl left right target-buf) 
-   (caml-types-feedback kind))))) 
+   (caml-types-feedback "call: %s" kind))))) 
    (if (and (= arg 4) 
       (not (window-live-p (get-buffer-window caml-types-buffer)))) 
     (display-buffer caml-types-buffer))