Đó là một quan niệm sai lầm phổ biến rằng chúng ta có thể dịch let
-expresions sang các ứng dụng. Sự khác biệt giữa let x : t := b in v
và (fun x : t => v) b
là trong phần let
-expression, trong quá trình kiểm tra kiểu v
chúng ta biết x
là bằng b
, nhưng trong ứng dụng chúng ta không (phần phụ fun x : t => v
phải có ý nghĩa riêng).
Đây là một ví dụ:
(* Dependent type of vectors. *)
Inductive Vector {A : Type} : nat -> Type :=
| nil : Vector 0
| cons : forall n, A -> Vector n -> Vector (S n).
(* This works. *)
Check (let n := 0 in cons n 42 nil).
(* This fails. *)
Check ((fun (n : nat) => cons n 42 nil) 0).
Đề xuất của bạn để làm cho ứng dụng (fun x : t => v) b
một trường hợp đặc biệt không thực sự hoạt động. Hãy để chúng tôi suy nghĩ về nó cẩn thận hơn.
Ví dụ, làm thế nào bạn sẽ đối phó với điều này, tiếp tục ví dụ trên?
Definition a := (fun (n : nat) => cons n 42 nil).
Check a 0.
Có lẽ điều này sẽ không hoạt động vì a
không thể gõ, nhưng nếu chúng ta mở ra định nghĩa của nó, chúng ta sẽ có một biểu thức được gõ tốt. Bạn có nghĩ rằng người dùng sẽ yêu chúng tôi, hoặc ghét chúng tôi vì quyết định thiết kế của chúng tôi?
Bạn cần suy nghĩ cẩn thận về "trường hợp đặc biệt" nghĩa là gì. Nếu tôi có một ứng dụng e₁ e₂
, tôi có nên bình thường hóa e₁
trước khi quyết định xem đó có phải là ứng dụng không? Nếu có, điều này có nghĩa là tôi sẽ bình thường hóa các biểu thức không được gõ và chúng có thể quay vòng. Nếu không, khả năng sử dụng đề xuất của bạn có vẻ nghi vấn.λ
Bạn cũng sẽ phá vỡ định lý cơ bản nói rằng mọi biểu thức con của biểu thức được gõ tốt đều được gõ tốt. Điều đó hợp lý như giới thiệu null
vào Java.
let
biểu thức, nhưng không có lý do gì để tránhlet
biểu thức và chúng cũng thuận tiện và b) thêm hack vào ngôn ngữ cốt lõi của bạn không phải là ý tưởng hay.