2015-11-03 60 views
1

在現實世界中OCaml中的this section,它基本上是說:OCaml的標記參數訂購帶有高階函數

let apply_to_tuple f (first, second) = f ~first ~second 
let apply_to_tuple_2 f (first, second) = f ~second ~first 
let divide ~first ~second = first/second 

這使得apply_to_tuple divide (3, 4)工作,但不apply_to_tuple_2 divide (3, 4)。後者拋出:

Error: This expression has type first:int -> second:int -> int 
    but an expression was expected of type second:'a -> first:'b -> 'c 

我想知道爲什麼會出現這種情況。看來這裏沒有任何含糊之處,編譯器可以正確推斷所有內容?

回答

2

只要您提供所有參數,OCaml允許您調用省略參數名稱。在這種情況下,參數按順序排列。因此,first:'a -> second:'b -> 'csecond:'b -> first:'a -> 'c的類型是不同的。

在我看來,爲了獲得想要的靈活性,您需要放棄無名稱呼叫的能力。

# let f ~a ~b = a - b;; 
val f : a:int -> b:int -> int = <fun> 
# f 4 3;; 
- : int = 1 

您可以指定f參數apply_to_tuple2,這使得打字工作,以特定的順序。

# let apply_to_tuple2 (f: first:'a -> second:'b -> 'c) (first, second) = 
    f ~second ~first;; 
val apply_to_tuple2 : (first:'a -> second:'b -> 'c) -> 'a * 'b -> 'c = <fun> 
# let divide ~first ~second = first/second;; 
val divide : first:int -> second:int -> int = <fun> 
# apply_to_tuple2 divide (3, 4);; 
- : int = 0 

更新

下面的是關於我聲稱什麼更多的一些細節。

首先,類型apply_to_tuple2divide

# let apply_to_tuple_2 f (first, second) = f ~second ~first;; 
val apply_to_tuple_2 : (second:'a -> first:'b -> 'c) -> 'b * 'a -> 'c = <fun> 
# let divide ~first ~second = first/second;; 
val divide : first:int -> second:int -> int = <fun> 

所以,apply_to_tuple2f參數的類型爲second:'a -> first:'b -> 'c。但分類的類型是first:int -> second:int -> int。這些類型不能統一,因爲命名參數的順序在OCaml中很重要。

如果您更改OCaml以使命名參數的順序無關緊要,可以使這些類型匹配。但這不是OCaml現在的工作方式。

此外,如果您確實進行了此更改,則還必須刪除OCaml的功能,因此您可以在某些情況下省略參數名稱。因此,這將是一種語言不兼容的變化。

+0

對。這是我的問題。在'apply_to_tuple_2'的定義中,我們使用帶標籤的參數調用了_did_,並且似乎沒有任何可能會使編譯器混淆的排序。 – chenglou

+1

'divide'的類型與'apply_to_tuple2'的'f'類型不匹配。這是基本問題。如果你想讓它們匹配,你可以聲明'f'的類型。 (推斷的類型不匹配。)如果您希望它自動工作,您必須更改OCaml類型系統的工作方式。所以我會說編譯器本身不會感到困惑。 –

+0

所以這在理論上是可行的嗎?只需通過匹配正確的標籤來安排訂單? – chenglou

2

雖然它可能看起來

first:int -> second:int -> int 

second:int -> first:int -> int 

是相同的,它們實際上是不是由於副作用。

考慮以下兩個功能:

let foo ~a = 
    print_endline "a"; 
    fun ~b -> 
    print_endline "b" 

let bar ~b = 
    print_endline "b"; 
    fun ~a -> 
    print_endline "a" 

foo具有同時bar鍵入a:'a -> b:'a -> unit的類型爲b:'a -> a:'a -> unitfoo在獲取第一個參數後打印"a",在獲取第二個參數後打印"b"bar在收到第一個參數後打印"b",在收到第二個參數後打印"a"

OCaml確保這些函數的副作用恰好在提供了該副作用之前的所有參數時發生。

所以foo一旦被賦予一個參數標記~a將打印"a"

# let foo2 = foo ~a:();; 
a 
val foo2 : b:'_a -> unit = <fun> 

,它將打印"b"一次都~a~b已提供:

# foo2 ~b:();; 
b 
- : unit =() 

bar韓元當給出標記爲~a

的論據時不打印任何東西
# let bar2 = bar ~a:();; 
val bar2 : b:'a -> unit = <fun> 

因爲這兩個打印語句都在~b參數之下。一旦它也給出了~b說法 - 這樣既~a~b已提供 - 這將同時打印"b""a"

# bar2 ~b:();; 
b 
a 
- : unit =() 

維護的副作用正確的順序是這樣要求的OCaml治療不同於所述第二標記的參數中的第一個標記的參數:

  • 當OCaml中看到的第一標記的參數的一個應用它必須應用的功能和執行它下面的任何副作用。

  • 當OCaml看到第二個參數的應用程序時,它必須建立一個等待第一個參數的新函數,當它接收到它時將使用第一個和第二個參數應用原始函數。

這意味着,在參數類型的順序是顯著的,你不能簡單地用一個second:int -> first:int -> int值,其中一個first:int -> second:int -> int值的預期。

OCaml可能會嘗試在運行時區分來自其他應用程序的第一個參數的應用程序,因此不需要在類型系統中跟蹤它,但這會使標記函數的效率遠低於常規功能。