2015-09-29 5 views
6

Когда я Исполнить кодЛиния OCaml производит таинственную ошибка

let (a,p) = (2+2, Printf.printf) in p "abc"; p "%d" 3 ;; 

Я ожидаю увидеть выход abc3, но получить вместо

File "f.ml", line 1, characters 46-47: 
Error: This function has type (unit, out_channel, unit) format -> unit 
     It is applied to too many arguments; maybe you forgot a `;'. 

Забавная частью является то, что если я изменю 2+2 в 2 , он работает.

Почему код вызывает ошибку как есть, но не с +2 удален?

+0

Я также заинтересован в ответ, это на первый взгляд странно. –

ответ

6

Комбинация специального трюка OCaml для printf и значения полиморфизма.

Как вы, возможно, знаете, Printf.printf не принимает string, но тип данных format. OCaml типа проверка имеет специальное правило для ввода строковых литералов для printf: если оно введено в format если контекст просит:

# "%d";; 
- : string = "%d" 
# ("%d" : _format);; 
- : (int -> 'a, 'b, 'a) format = ... 

система типа OCaml имеет еще один трюк называется значение полиморфизма (точнее, расслаблено значение полиморфизм). Его цель - правильно набирать выражения с побочными эффектами. Я не объяснить его деталь, но он ограничивает полиморфизм: некоторые формы выражения называется «экспансивным» не может иметь полиморфные типов:

# fun x -> x;; 
- : 'a -> 'a = <fun> 
# (fun x -> x) (fun x -> x) 
- : '_a -> '_a = <fun> 

В выше, (fun x -> x) (fun x -> x) не имеет полиморфный типа, в то время как функция тождества fun x -> x имеет. Это связано с формой выражения (fun x -> x) (fun x -> x): он «экспансивный». Странная переменная типа '_a является переменной мономорфного типа: она может быть создана для некоторого типа только один раз. С другой стороны, полиморфные переменные, такие как 'a, могут быть созданы для разных типов для каждого использования.

Давайте вернемся к коду:

# let (a, p) = (2, Printf.printf);; 
val a : int 
val p : ('a, out_channel, unit) format -> 'a 

Здесь p имеет полиморфный тип ('a, out_channel, unit) format -> 'a. 'a может быть экземпляр более чем для одного типа, поэтому p "abc"; p "%d" 3 является типичным: полиморфный тип может быть создан для (unit, out_channel, unit) format -> unit для первого использования p и (int -> unit, out_channel, unit) format -> int -> unit для второго использования p.

После того, как вы изменить константу 2 к 2+2, что экспансивный, все выражение становится экспансивной тоже, и набрав изменения:

# let (a, p) = (2+2, Printf.printf);; 
val a : int 
val p : ('_a, out_channel, unit) format -> '_a 

Здесь p не имеет больше не полиморфный переменные 'a но мономорфический '_a. Эта мономорфная переменная унифицирована (создана) до unit при первом использовании p, и в результате p тип становится (unit, out_channel, unit) format -> unit. Это может принимать только один аргумент, поэтому ввод второго использования p с 2 аргументами не выполняется.

Один простой способ избежать этой ситуации, чтобы разделить свое определение на два:

let a = 2 + 2 in 
let p = Printf.printf in 
p "abc"; p "%d" 3