Đôi điều về em Yến

Sau một thời gian tìm hiểu kha khá dài thì nay mình xin viết vài dòng (vài trăm dòng thì đúng hơn) về em Yến (ngôn ngữ lập trình Swift) để thoả lòng tò mò của những ai quan tâm. Vì kiến thức của mình về các ngôn ngữ lập trình cũng còn non nên nếu có gì sai sót các đại ca đi qua xin nhẹ tay. Thêm điều nữa là đây là bài viết tổng hợp nên mình không đi vào chi tiết vấn đề cú pháp câu lệnh của em Yến lắm. Mình cũng sẽ tập trung phân tích những cái khác biệt của em í chứ không nói nhiều đến những thứ giống. Mọi người muốn biết thêm về Swift thì tham khảo trên trang web của Apple.


Vì sao em Yến ra đời

Trước khi đi vào chi tiết từng góc cạnh của em Yến, thiết nghĩ nên tìm hiểu vì sao em ấy lại được sinh ra. Nói về sự ra đời của em Yến thì dễ nhất là trích lời giới thiệu của Apple (được dịch một cách sơ sài nhất) đó là : “…để cung cấp một ngôn ngữ mới, mạnh mẽ hơn, linh hoạt hơn (như Python, Ruby, các ngôn ngữ script,…), nhưng đồng thời cũng lưu giữ lại những điểm mạnh của Objective C.”

Còn nói cho hiểu rõ ngọn ngành của vấn đề thì là thế này : 

  • Thứ nhất, bởi vì Objective C (ngôn ngữ chính để lập trình cho iOS và OS X) hơi lạ so với các ngôn ngữ lập trình phổ biến khác như C#, Java, PHP, nên những lập trình viên khi chuyển từ các ngôn ngữ lập trình đó sang (để viết ứng dụng cho iDevices) thì phải mất thời gian tìm hiểu để nắm vững nguyên lý làm việc. Do đó, Swift đã vứt bỏ nhiều thứ là căn bản của Objective C và dùng nguyên lý của các ngôn ngữ C#, Java. Nên giờ, Swift đã gần với các ngôn ngữ này hơn. 
  • Thứ hai là các ngôn ngữ mới sau này hiện đại hơn các ngôn ngữ kiểu đa năng hồi xưa (C, C++, C#, Java) ở chỗ nó hỗ trợ nâng cao hiệu năng lập trình. Kiểu như một bên cần viết 100 dòng lệnh thì bên này giờ chỉ cần viết 1 dòng. Ví dụ: để thực hiện nhân ma trận, C# cần ít nhất là 3 dòng lệnh, thì Python chỉ cần 1 dòng (A * B). Nói chung, giờ Swift hiện đại hơn so với Objective C nhiều.
Hai điều trên cũng là hai lý do khiến mình vừa không thích, vừa thích em Yến.

Như vậy, em Yến ra đời với mục tiêu rất rõ ràng rồi. Tuy nhiên, có nhiều người sẽ tự hỏi là sao Apple không cải tiến ngôn ngữ Objective C luôn, mà phải tạo ra một ngôn ngữ mới làm gì. Về vấn đề này thì mình xin đưa ra hai luận giải như sau :

  1. Thực sự thì Objective C qua từng năm một thì đều có nhiều cải tiến, cả về hiệu năng cũng như cú pháp câu lệnh. Tuy nhiên, Swift mới hơn, linh động hơn, cởi mở hơn, an toàn hơn, hiện đại hơn nhiều. Nếu áp dụng ngay vào Objective C thì tất cả các lập trình viên đều bị tác động. Do đó, hiện nay Apple hỗ trợ cả hai để ai thích thì bắt đầu với Swift, các dự án cũ với Objective C vẫn có thể tiếp tục được phát triển. Hai ngôn ngữ này sống chung với nhau trong một dự án vẫn được.
  2. Swift sử dụng nhiều nguyên lý từ các ngôn ngữ C++, C#, Java, các nguyên lý này khác rất nhiều so với Objective C. Do đó, để đùng một cái đổi Objective C quay 180 độ thì ai mà theo cho kịp. Cộng thêm tốn thời gian công sức của lập trình viên để học cái mới thì cũng không ổn.

Có một điểm phải làm rõ trước khi so sánh em Yến với các đàn chị C++, C# và Java. Đó là em Yến được thừa kế, phát triển lên từ C và Objective C, một nhánh rất khác so với C# và Java. Do đó, trong tài liệu của Apple, họ so em Yến với C và Objective C. Cũng chính từ điều này, nên nhiều phần Apple có vẻ ca ngợi em Yến, nhưng thực tế các ngôn ngữ lập trình khác đã hỗ trợ rồi.

Nói như vậy thì có vẻ em Yến chẳng có gì mới. Xin thưa là không đúng, vì em ấy có nhiều cái mới so với Objective C, cũng như nhiều cải tiến khá mới lạ so với C#, Java.

Thôi mình không dài dòng nữa mà sẽ đi vào từng đường cong của em í.

Cơ bản

  • Đầu tiên, em Yến tỏ ra linh hoạt trong điều nhỏ nhất của một ngôn ngữ lập trình. Đó là, dấu chấm phẩy. Dấu chấm phẩy được đại đa số các ngôn ngữ lập trình sử dụng để làm ký tự kết thúc một dòng lệnh. Còn với em Yến, dấu chấm phẩy vẫn với tác dụng là kết thúc một dòng lệnh đó. Tuy nhiên, các bạn muốn hay không muốn dùng dấu chấm phẩy đều được. Mà nếu đã có thể bỏ đi thì thôi ta bỏ đi (hãy đừng là người hoài cổ - :)).
Thực tế, thì vẫn có một số trường hợp bạn cần dùng đến dấu chấm phẩy. Ví dụ: viết nhiều lệnh trên cùng 1 dòng. Do đó, việc cho phép dùng hoặc không dùng dấu chấm phẩy là điều rất tuyệt vời.

Biến và hằng

  • Tương tự các ngôn ngữ lập trình khác, em Yến cũng dùng biến và hằng để lưu dữ liệu. Tuy nhiên, em ấy giống các ngôn ngữ dạng script hơn vì cho phép khai báo biến và hằng theo kiểu động, nghĩa là không cần xác định kiểu dữ liệu của biến và hằng (Xem ví dụ bên dưới).

Khai báo biến thì dùng từ khoá var, khai báo hằng thì dùng từ khoá let
  • Tuy nhiên, mọi người đừng nghĩ rằng em í đang dùng 1 kiểu dữ liệu chung cho tất cả. Không phải là như thế, mà em í sẽ tự động định kiểu cho biến, hằng theo kiểu dữ liệu của giá trị được gán.
Với ví dụ ở trên thì cả hai biến và hằng đều có kiểu dữ liệu là Int

Lúc này, có người sẽ nghĩ đến trường hợp khi người ta khai báo biến nhưng chưa gán giá trị thì làm sao em Yến chọn kiểu dữ liệu cho biến. Với trường hợp này, em Yến tạm thời để biến đó chưa cấp phát vùng nhớ vội (bởi vì kích thước vùng nhớ của biến phụ thuộc vào kích thước vùng nhớ mà mỗi kiểu dữ liệu hỗ trợ) chờ đến khi biến đó được gán giá trị đầu tiên thì mới định kiểu dữ liệu và cấp phát bộ nhớ cho biến.

Em Yến cũng áp dụng điều trên cho hằng. Tức là, không giống các ngôn ngữ khác, em Yến cho phép khai báo hằng mà không cần phải khởi gán ngay giá trị của hằng, giá trị của hằng có thể được gán ngay cả khi chương trình đang chạy. Nhưng sau khi đã gán giá trị khởi tạo cho hằng thì không thể thay đổi giá trị của hằng được nữa. Điều này là hữu ích vì thực tế có nhiều hằng số chỉ khi chạy chương trình thì mới xác định được giá trị.

Tuy nhiên, bạn vẫn có thể xác định kiểu dữ liệu cho biến, hằng ngay khi khai báo sử dụng cú pháp như ví dụ sau:

  • Em Yến được thiết kế để trở thành một ngôn ngữ an toàn, nên em í chỉ cho sử dụng biến, hằng sau khi đã khởi gán giá trị cho biến, hằng. Nếu bạn cố sử dụng biến, hằng khi nó chưa được khởi gán thì chương trình sẽ phát sinh lỗi. Điều này là tương tự C#, khác so với C, C++.
  • Để chèn giá trị của biến và hằng vào chuỗi, em Yến hỗ trợ kỹ thuật tương tự token giữ chỗ của C#, nhưng thay vì đánh số thứ tự các token giữ chỗ như C# thì em Yến cho phép dùng thẳng tên biến, hằng luôn (như ví dụ dưới đây).

Kỹ thuật của em Yến có một vài điểm mạnh hơn so với token giữ chỗ. Đầu tiên, token giữ chỗ của C# thì bắt buộc phải thông qua hàm Format của lớp String hoặc hàm Write và WriteLine của lớp Console. Còn bên em Yến thì cú pháp tự nhiên hơn rất nhiều. Thứ hai, việc dùng tên của biến thay cho các số 0, 1, 2... sẽ giúp cho lập trình viên dễ hiểu, dễ kiểm soát câu lệnh hơn.

Kiểu dữ liệu

  • Em Yến cung cấp đầy đủ các kiểu dữ liệu cơ bản như : Int, Double, Float, Bool, String, Character
  • Với em Yến, tất cả các kiểu dữ liệu (ngoại trừ kiểu lớp) đều là kiểu giá trị. Duy nhất kiểu lớp là kiểu tham chiếu. Như vậy, kiểu String, Array, Dictionary, Enum, Structure đều là kiểu giá trị. Đây là một khác biệt rất lớn so với các ngôn ngữ khác và nó tạo ra nhiều thuận tiện, dễ dàng trong việc xử lý cho người lập trình.

Kiểu giá trị là kiểu mà khi thực hiện gán hoặc truyền tham số cho hàm thì giá trị của biến, hằng sẽ được sao chép. Do đó, một biến mảng khi được gán thì với em Yến nó sẽ sao chép cả mảng sang biến mới.

Kiểu tham chiếu là kiểu mà khi thực hiện gán hoặc truyền tham số cho hàm thì địa chỉ của biến sẽ được sao chép.

  • Giống C# và Java, em í cung cấp đầy đủ các kiểu dữ liệu như Int32, UInt8, Int16, UInt64, Double, Float,... Tuy nhiên, khi làm việc với số nguyên, nếu bạn không quan tâm đến kích thước vùng nhớ lắm thì nên dùng kiểu dữ liệu toàn năng được em Yến cung cấp là : Int, UInt. Các kiểu dữ liệu này là các kiểu dữ liệu thông minh, nó sẽ tự thay đổi kích thước theo nền tảng mà bạn đang chạy chương trình - tức là, nếu nền tảng là 32 bit thì kích thước sẽ là 32 bit, trên nền tảng 64 bit thì kích thước sẽ là 64 bit.
  • Em Yến là an toàn kiểu, nên nó sẽ không cho phép bạn gán một số nguyên cho một biến kiểu String, hoặc gán một ký tự (kiểu Character) cho một biến kiểu Int (như C, C++). Em í còn an toàn hơn nữa ở chỗ, em loại bỏ cú pháp ép kiểu ngầm định.
Em í bắt các anh phải ép kiểu một cách rõ ràng (ép kiểu tường minh).

Cú pháp ép kiểu tường mình của em í cũng rất khác biệt (khác so với C, C++, Objective C, C#, Java), xem ví dụ bên dưới. 

Thật ra, đây là cú pháp gọi hàm cấu tử hay còn gọi là hàm cấu tử chuyển đổi kiểu.

Đỡ tốn công phải biên dịch câu lệnh
  • Để cho dễ đọc các số lớn, em í hỗ trợ cách phân tách phần ngàn như sau :

Các anh thấy dễ đọc chưa ?
  • Em í hỗ trợ tạo bí danh cho các kiểu dữ liệu. Điều này rất hay vì nó sẽ cung cấp các tên kiểu dữ liệu mới phù hợp hơn với ngữ cảnh. Ví dụ: bạn dùng tên kiểu dữ liệu là điểm để tạo các biến chứa điểm thì sẽ dễ hiểu hơn là bạn khai báo các biến đó với kiểu dữ liệu là UInt8.

typealias Diem = UInt8

var diemToan : Diem

Kiểu String và kiểu ký tự

  • Điểm khác biệt đáng quan tâm nhất về String là khả năng sửa đổi (mutability). Trong Objective C cũng như C#, các chuỗi đều là chuỗi bất di bất dịch (immutable), có nghĩa là sau khi gán một chuỗi cho biến thì chúng ta không thể chèn thêm ký tự, xoá ký tự, thay đổi ký tự trong chuỗi đó. Nếu bạn muốn làm những việc như vậy, bạn phải dùng kiểu NSMutableString (của Objective C) hay kiểu StringBuilder (của C#). Điều này đôi lúc gây ra sự bất tiện và làm gia tăng việc xử lý chuỗi. Còn với em Yến, việc mutable hay immutalbe được quyết định thông qua bạn gán chuỗi cho biến hay hằng. Nếu bạn gán chuỗi cho hằng thì chuỗi đó là immutable và bạn gán cho biến thì bạn có thể sửa đổi chuỗi đó tuỳ thích. Một cách xử lý khá là giản dị.
  • Như ở phần trên đã đề cập tới, String của em Yến là kiểu giá trị và khi bạn thực hiện gán hoặc truyền tham số thì chuỗi sẽ được sao chép. Cách xử lý này gần với tự nhiên hơn, nên người lập trình sẽ dễ dàng hơn để sử dụng. Tuy nhiên, có người sẽ bảo rằng như vậy thì sẽ tốn hiệu năng xử lý hơn rất nhiều (vì sao chép luôn cả chuỗi như vậy vừa tốn bộ nhớ, vừa tốn hiệu năng xử lý sao chép chuỗi). Em Yến đã xử lý vấn đề này ngầm bên dưới bằng cách : đầu tiên, vẫn chỉ gán địa chỉ (không sao chép nguyên chuỗi ra vùng nhớ khác), chỉ cho đến khi gặp dòng lệnh thực hiện sửa đổi nội dung của chuỗi thì chuỗi đó mới được sao chép ra vùng nhớ mới và các thao tác sửa đổi được thực hiện trên vùng nhớ này.
  • Có thể dùng phép toàn + để nối chuỗi, ký tự; phép toán so sánh ==, != để so sánh hai chuỗi, hai ký tự; phép toán += để bổ sung chuỗi, ký tự vào cuối.
Bên C#, Java thì chỉ dùng các hàm

Kiểu dữ liệu Tuple

  • Kiểu tuple là một trong hai kiểu dữ liệu mới của em Yến.
  • Kiểu tuple nhóm nhiều giá trị vào bên trong một giá trị kết hợp duy nhất. Các giá trị trong tuple có thể thuộc bất kỳ kiểu dữ liệu nào và không cần phải giống nhau.

Bên dưới là câu lệnh khai báo một hằng kiểu tuple và gán một tuple gồm hai giá trị (một số nguyên, một chuỗi) cho hằng này. Tuple (404, "Not Found") nhóm một số nguyên và một chuỗi lại với nhau.

Một nguyên mẫu tuple được bao lại trong cặp dấu ngoặc

Bạn có thể tách một tuple ra các biến, hằng dùng lệnh dạng như sau :

statusCode và statusMessage là hai hằng. Sau khi thực hiện lệnh này, bạn có thể tách hai hằng này để sử dụng.

hoặc nếu bạn không muốn nhận giá trị của thành phần nào của tuple thì bạn có thể dùng dấu gạch chân để bỏ qua.

  • Bạn có thể truy xuất vào các thành phần của tuple thông qua chỉ mục :

hoặc khi gán giá trị thì bạn đặt tên cho các thành phần để sau đó sử dụng :

  • Kiểu tuple phát huy sự hiệu quả trong khá nhiều trường hợp, như với kiểu trả về của hàm, dùng với câu lệnh switch, dùng với lệnh lặp.

Ví dụ dưới đây sử dụng tuple để so sánh hai biến bool có cùng giá trị hay không :

switch (a, b) {

       case (true, true), (false, false): return true

    default: return false

}

Với hàm, thông thường, chúng ta chỉ có thể trả ra một giá trị thông qua câu lệnh return. Nếu muốn trả ra nhiều hơn thì chúng ta phải sử dụng danh sách tham số và truyền tham số theo tham chiếu (còn gọi là tham biến). Tuy nhiên, với cách dùng này thì câu lệnh khá tối nghĩa vì vừa là dùng tham số đề truyền tham số vào vừa để nhận giá trị ra. Chưa kể đến việc các biến trước khi được truyền dạng tham chiếu còn phải bắt buộc được khởi gán giá trị (ví dụ trong C#), nhưng đâu phải lúc nào bạn cũng biết giá trị để khởi tạo. Do đó, tuple là hình thức tự nhiên nhất để trả ra nhiều giá trị thông qua câu lệnh return.

  • Nếu suy nghĩ thêm chút về cách thức nào em Yến hỗ trợ tuple thì mình nghĩ em Yến dùng structure, nhưng có điều structure này hỗ trợ trường hợp khá đặc biệt là structure này không có tên.

Kiểu dữ liệu Optional

  • Đây là kiểu dữ liệu mới thứ hai
  • Bạn dùng optional khi một biến (tại thời điểm nào đó trong chu trình sống của nó) có thể không chứa giá trị nào. Khi bạn lấy giá trị của một biến optional thì bạn có thể nhận được câu trả lời kiểu như : Có chứa giá trị và nó là x hoặc Hoàn toàn không chứa giá trị nào.
  • Optional gần giống như con trỏ nil (hoặc null của C#). Tuy nhiên, nil hay null chỉ dùng với kiểu dữ liệu lớp, còn optional thì dùng với mọi kiểu dữ liệu. Có nghĩa là ngay cả một biến kiểu Int cũng có thể có lúc không nhận được giá trị nào. Em Yến cũng dùng từ khoá nil, nhưng nó không phải là con trỏ (trong C nó là con trỏ trỏ về vùng đầu tiên của bộ nhớ heap), nó chỉ là từ khoá để chỉ tình trạng không chứa giá trị.
  • Để khai báo một kiểu optional, ta thêm dấu chấm hỏi vào cuối tên kiểu dữ liệu. Ví dụ : Int?, String?
  • Có thể dùng câu lệnh if để kiểm tra xem một biến optional có chứa dữ liệu hay không bằng cách so sánh với giá trị nil:

Khi đã xác định được biến optional có chứa giá trị thì bạn thêm dấu chấm cảm vào sau tên biến để lấy giá trị của biến đó. Lấy giá trị của biến optional như thế này người ta gọi là mở gói ép buộc (forced unwrapping).

Bạn có thể sử dụng cú pháp Optional Binding để vừa kiểm tra sự tồn tại của giá trị vừa lấy giá trị ra nếu có.

Nếu có giá trị thì biểu thức điều kiện sẽ nhận được giá trị true và giá trị của biến possibleNumber sẽ được gán cho hằng actualNumber.

Vị trí từ khoá let có thể thay bằng var.

Optional Binding có thể dùng trong cả vòng lặp while.

  • Trong một số trường hợp, bạn biết rằng một biến optional sẽ luôn có giá trị sau khi được khởi gán. Lúc này, việc kiểm tra và mở gói là không cần thiết nữa. Do đó, bạn có thể sử dụng kiểu optional mở gói ngầm định (implicit unwrapped optional). Để khai báo, bạn dùng dấu ! thay cho dấu hỏi. Ví dụ : Int!

Để lấy giá trị của biến optional mở gói ngầm định thì không cần dấu chấm cảm theo sau.

Trường hợp bạn sẽ dùng kiểu optional mở gói ngầm định : Quan hệ giữa thủ đô (capital city) và nước (country). Vì mỗi nước có 1 thủ đô, 1 thủ đô phải thuộc về một nước. Do đó, sau khi khởi tạo xong một nước thì sẽ có một liên kết (không nil) đến một thủ đô và ngược lại.

  • Objective C là ngôn ngữ thuần hướng đối tượng - có nghĩa là mọi thứ trong Objective C đều là đối tượng (không như các ngôn ngữ hướng đối tượng khác như C#, Java thì vẫn có cái không phải là đối tượng). Ngay cả con trỏ nil cũng là đối tượng. Bởi vì, nó là đối tượng nên có thể truyền thông điệp cho nó. Em Yến được phát triển lên từ Objective C, nhưng để gần với C#, Java, nó đã chuyển sang dùng nguyên lý lời gọi hàm. Tuy nhiên, để khắc phục việc gọi hàm không có, kiểu optional và kỹ thuật optional chaining là giải pháp cực kỳ hữu hiệu.
Trong Objective C, người ta dùng nguyên lý truyền thông điệp chứ không phải là lời gọi hàm. Hai nguyên lý này rất khác biệt và nó ảnh hưởng đến cách thức hoạt động của hệ thống. Theo lời gọi hàm, thì hàm đó bắt buộc phải có, chương trình mới có thể gọi. Nếu không có thì chương trình bị lỗi. Còn theo nguyên lý truyền thông điệp thì dù thông điệp chưa được cụ thể hoá bằng hàm xử lý thì thông điệp đó sẽ vẫn được thụ lý bằng hàm khác (hàm xử lý chung). Do đó, với nguyên lý truyền thông điệp thì chương trình sẽ không bao giờ phát sinh lỗi.

Dùng dấu chấm hỏi đặt sau biến optional để kiểm tra và mở gói.

Toán tử

  • Phép gán = không trả ra giá trị, nên không gán giá trị cho nhiều biến đồng thời như trong C# được. Việc ngăn không cho phép gán trả ra giá trị cũng giúp các biểu thức điều kiện đỡ phức tạp, đỡ sai sót hơn.
Ví dụ: bạn muốn dùng phép so sánh ==, nhưng lở tay chỉ chấm 1 dấu bằng thì trong C, C++ câu lệnh đó vẫn đúng và không báo lỗi
  • Toán tử % có thể dùng cho cả số thực và số âm
  • Các phép toán số học (+, -, *, /) mặc định không xử lý trường hợp tràn số. Nếu tràn số thì chương trình sẽ phát sinh lỗi.
Tràn số là khi mà giá trị gán cho biến, hằng vượt quá phạm vi lưu trữ của kiểu dữ liệu của biến, hằng đó.

Trong đại đa số các ngôn ngữ lập trình, thì nếu tràn số xảy ra, giá trị của biến, hằng sẽ nhận được là giá trị nhỏ nhất trong miền giá trị của kiểu dữ liệu của biến, hằng đó.

  • Để xử lý trường hợp tràn số, em Yến hỗ trợ 5 toán tử xử lý tình huống tràn số là &+, &-, &*, &/, &%. Bên dưới là ví dụ sử dụng phép toán &+.

Phép toán &/ đã xử lý cả trường hợp chia cho 0.

  • Toán tử ?? (Nil Coalescing) được dùng để mở gói một biến kiểu optional nếu biến đó có giá trị, nếu không sẽ trả ra giá trị mặc định (nằm sau toán tử ??).

  • Toán tử phạm vi ..< và ... Ví dụ, ta viết a...b để lấy các giá trị trong đoạn từ a đến b. Ta viết a..<b để lấy các giá trị từ a đến cận dưới của b. Hai toán tử này khá hữu ích, có thể dùng thay thế cho các cú pháp câu lệnh for đã cũ và không rõ ràng.

Array và Dictionary

  • Giống như Objective C, em Yến vẫn chỉ cung cấp hai kiểu collection là Array và Dictionary.

Array (còn gọi là mảng) lưu trữ một danh sách có thứ tự các giá trị có cùng kiểu. Các giá trị giống nhau có thể xuất hiện nhiều lần tại những vị trí khác nhau.

Dictionary lưu trữ một tập không thứ tự các giá trị cùng kiểu mà nó có thể được tham chiếu và tìm kiếm thông qua một định danh duy nhất (hay còn gọi là khoá). Dictionary chính là kiểu tập hợp Map hoặc Hashtable trong C# và các ngôn ngữ khác.

Bạn dùng dictionary khi bạn cần tìm kiếm các giá trị dựa trên định danh (khoá) của chúng, giống như cách bạn tìm kiếm định nghĩa của các từ trong một quyển từ điển.

Objective C chỉ cung cấp đúng hai kiểu collection này. Các kiểu collection như List, Stack, Queue là không có. Nhưng trong XCode, người ta dùng hai kiểu này khá linh hoạt để thay thế cho những kiểu thiếu trên. Có trường hợp khá thú vị và hay là người ta dùng Dictionary để tổ chức dữ liệu cho các TableView. Mỗi hàng là một thành phần trong Dictionary được phân biệt với nhau thông qua key là id của hàng dữ liệu. Việc tổ chức bằng Dictionary như trên mạnh mẽ và linh hoạt hơn tổ chức bằng Array rất nhiều.

  • Array và dictionary của em Yến phải được định kiểu rõ ràng. Khác so với Objective C là kiểu nào cũng chơi.
  • Thật sự thì array và dictionary của em Yến được cài đặt là các tập hợp generic (xem phần Khuôn mẫu (generic) bên dưới). Do đó, nó có hiệu năng cao hơn so với array và dictionary của Objective C rất nhiều.
  • Tương tự như kiểu string thì array và dictionary là có thể sửa đổi (mutable) khi được chứa trong biến và không thể sửa đổi (immutable) khi được chứa trong hằng. Điều này giải phóng người lập trình ra khỏi cái mớ bòng bong giữa NSArray và NSMutableArray hay NSDictionary và NSMutableDictionary của Objective C.
  • Các thao tác thêm, sửa, xoá của em Yến khá là tự nhiên. Bạn có thể dùng phép toán += để thêm một hoặc vài thành phần mới vào array hoặc dictionary. Bạn có thể dùng toán tử phạm vi để sửa đổi cùng lúc cả nhóm các thành phần liền kề nhau (xem ví dụ dưới).

  • Để duyệt qua các thành phần của array, dictionary thì bạn dùng lệnh for-in.

Câu lệnh trên giống lệnh foreach của C#, nhưng mạnh hơn một xí. Ví dụ :

Sử dụng tuple và hàm enumerate để lấy cả giá trị và vị trí của giá trị. Hữu ích hơn việc dùng câu lệnh for duyệt qua từng thành phần, vì nó không rõ ràng, dễ hiểu.
  • Trong thực tế thì string, array và dictionary được cài đặt sử dụng struct (xem phần Class và structure bên dưới để hiểu về struct). Điều đó có nghĩa rằng chúng sẽ được sao chép khi thực hiện gán cho một biến, hằng mới, hoặc khi được truyền tham số cho hàm.

Hành vi này là khác biệt so với các kiểu này trong Objective C, cũng như C#, Java.

Các cấu trúc điều khiển

  • Em Yến cung cấp đầy đủ các câu lệnh điều khiển if, switch, for, while, do.
  • Các biểu thức điều kiện không cần đặt trong cặp dấu ngoặc, nhưng cặp dấu ngoặc nhọn để bao phần thân là bắt buộc.
Mình thấy điều này cũng như không và đôi lúc không hữu ích. Ví dụ trường hợp phần thân chỉ có 1 câu lệnh, nhưng vẫn phải bắt buộc có cặp dấu ngoặc nhọn.
  • Các biểu thức điều kiện phải có kết quả kiểu bool. Điều này là an toàn hơn, giống với C#, Java, nhưng khác so với C.
  • Câu lệnh switch là câu lệnh có nhiều cải tiến nhất trong các câu lệnh điều khiển và nó có cú pháp chung như sau :

Thứ nhất, một case có thể xử lý cho nhiều giá trị (khác với C#). Nếu nhiều case thoả điều kiện thì case đầu tiên (ở trên cùng) sẽ được lựa chọn thực hiện.

Thứ hai, không cần câu lệnh break vì mặc định em Yến không cho phép tự chạy xuống case tiếp theo mà sẽ thoát ra khỏi câu lệnh switch. Điều này ngăn người lập trình khỏi các lỗi do quên câu lệnh break (Nhưng nếu muốn bạn vẫn có thể dùng câu lệnh break ở đây). 

Thứ ba, mỗi case phải có ít nhất một câu lệnh thực thi nào đó. 

Thứ tư, có thể dùng toán tử phạm vi để so khớp một dãy giá trị liên tục. 

Thứ năm, có thể dùng kiểu tuple để mở rộng khả năng của switch.

Thứ sau, có thể dùng value binding trong switch. Ví dụ: 

Cuối cùng và mạnh mẽ nhất là nó còn hỗ trợ mệnh đề where. Ví dụ:

  • Câu lệnh for-in là câu lệnh mới được bổ sung trong nhóm các câu lệnh lặp. Câu lệnh này giống câu lệnh foreach của C#, nhưng mạnh mẽ hơn. Vì một số phần ở trên cũng đã đề cập đến câu lệnh này rồi, nên ở đây chỉ nhắc lại cách dùng đơn giản nhất của for-in, như sau:

index trong câu lệnh trên là một hằng. Bạn không cần phải khai báo trước khi sử dụng, em Yến đã tự động làm cho bạn rồi. Hằng này chỉ tồn tại bên trong vòng lặp for-in đó.

Bạn có thể bỏ qua các index trên sử dụng dấu gạch chân như sau:

  • Em Yến vẫn hỗ trợ hai câu lệnh break và continue. Ngoài ra, còn hỗ trợ thêm câu lệnh fallthrough (trong câu lệnh switch) để cho phép thực thi xuống tiếp case tiếp theo, giống như cách làm việc của switch của các ngôn ngữ C, C++, C#.
  • Em Yến hỗ trợ một thứ gọi là labelled statement. Cái này nó giống như nhãn và lệnh goto trong C, C++. Tuy nhiên, labelled statement được dùng trong các trường hợp như : khi bạn có nhiều vòng lặp lồng nhau, hoặc khi bạn có các câu lệnh lặp lồng trong câu lệnh switch,... Trong các trường hợp này, việc câu lệnh break, continue có thể xác định được thoát ra khỏi vòng lặp, câu lệnh switch nào thì sẽ hữu dụng hơn. Như vậy cách dùng labelled statement an toàn hơn so với lệnh goto của C, C++ rất nhiều.

Hàm

Hàm là một nhóm các dòng lệnh được nhóm lại trong một tên hàm, nhằm thực hiện một nhiệm vụ cụ thể nào đó. Ví dụ: hàm để tính tổng hai số nguyên, hàm để in một số nguyên dưới dạng số nhị phân, hàm để tìm kiếm một số trong mảng các số,...
  • Cú pháp khai báo hàm như sau :

Đến ngang đây thì mọi người gần hiểu vì sao khai báo kiểu dữ liệu thì cú pháp lại đặt kiểu dữ liệu nằm sau tên biến.
  • Có thể dùng kiểu tuple làm kiểu của tham số cũng như giá trị trả về.
  • Với em Yến, tham số của hàm theo khai báo thông thường được gọi là tham số cục bộ (local parameter name). Các tham số này chỉ được dùng bên trong phạm vi của hàm. Ngoài ra, em í còn hỗ trợ tham số ngoài (external parameter name) được dùng khi gọi hàm. Mục đích thứ nhất của nó là để chỉ định chức năng của các giá trị khi truyền cho hàm, mục đích thứ hai là để lời gọi hàm gần giống như một câu văn.
Cái này tương tự như tham số đặt tên trong C#, nhưng mục đích của C# là để cho phép việc truyền tham số không cần theo thứ tự trong định nghĩa hàm, chứ không phải để lời gọi hàm trông giống một câu văn.

string, toString, withJoiner là các tên tham số ngoài, còn s1, s2, joiner là tên tham số cục bộ dùng trong hàm. Lúc này, lời gọi hàm sẽ gần như một câu văn “Nối chuỗi hello vào chuỗi world với từ nối là “,””. Lời gọi hàm lúc này rõ ràng, dễ đọc, dễ hiểu hơn.

Một đặc điểm nổi bật và khác người của Objective C chính là những lời gọi hàm (Objective C gọi là truyền thông điệp). Nó khác biệt bởi vì thứ nhất nó theo nguyên lý truyền thông điệp, thứ hai các lời gọi hàm giống như một câu văn (hoặc có thể diễn đạt dễ dàng thành câu văn mà chỉ cần thêm rất ít từ bổ trợ vào). Ví dụ với lời gọi hàm [counter incrementBy: 5 numberOfTimes: 3]; có thể diễn đạt thành "Biến counter tăng lên 5 đơn vị 3 lần liên tục".

Em Yến cũng được thiết kế giữ lại những điểm mạnh này. Tuy nhiên, cặp dấu ngoặc trong lời gọi hàm làm cho câu văn không được đẹp lắm.

  • Em Yến cung cấp dạng tham số biến đổi (variadic parameters) tương tự như tham số param trong C#. Xem ví dụ để hiểu.
  • Các tham số của hàm mặc định được xem là các hằng. Trình biên dịch sẽ báo lỗi khi bạn cố gắng thay đổi giá trị của các tham số này. Đây là điều khá đặc biệt.
Trong các ngôn ngữ khác thường dùng khái niệm truyền tham trị và truyền tham biến (tham chiếu) (như Pascal, C, C#, Java). Tuy nhiên, các ngôn ngữ này dùng các biến để nhận giá trị (với truyền theo tham trị) nên vẫn giá trị có thể bị thay đổi mà trình biên dịch không phát sinh lỗi.

Khi bạn muốn giá trị truyền vào cho một biến thì bạn thêm từ khoá var vào phía trước tên tham số.

  • Em Yến cũng hỗ trợ dạng truyền tham chiếu (tham biến) thông qua tham số in-out. Sử dụng tham số này bằng cách thêm từ khoá inout ở phía trước tên tham số.

Kiểu hàm
  • Mỗi hàm đều có một kiểu dữ liệu gọi là kiểu hàm và được kết hợp từ kiểu của các tham số và kiểu trả về. 

Hai hàm trên đều có chung kiểu hàm là (Int, Int) -> Int
  • Kiểu hàm này có thể sử dụng như các kiểu dữ liệu khác của em Yến, có nghĩa là có thể khai báo biến, hằng, dùng làm kiểu của tham số, kiểu trả về của hàm,…
Kỹ thuật bên dưới của cái này gọi là con trỏ hàm, nhưng với em Yến thì dễ hiểu và dễ sử dụng hơn.

Ví dụ trên tạo ra một biến kiểu hàm (Int, Int) -> Int có tên là mathFunction và gán hàm addTwoInts cho nó.

Sau đó, có thể dùng biến mathFunction để gọi hàm.
  • Kiểu hàm chính là cơ sở cho các kỹ thuật như delegate và event (sự kiện)

Closure

  • Closure giống với inline function của Pascal, C, C++, giống lambda của C# và chính là block trong Objective C.
  • Closure là dạng hàm đặc biệt mà phần cài đặt của nó nằm ngay trong lời gọi hàm đến một hàm khác. Closure không có tên và thường là các hàm nhỏ (chỉ 1, 2 dòng lệnh). Closure có thể truy xuất các hằng, biến nằm trong ngữ cảnh nơi nó được định nghĩa.
  • Để hiểu rõ cách dùng closure thì chúng ta nên đi qua ví dụ ngắn sau. Đầu tiên, chúng ta có mảng sau:

Chúng ta dùng hàm sorted để sắp xếp mảng này. Hàm này nhận hai tham số :

  1. Mảng cần sắp xếp,
  2. Đối số kiểu hàm (<kiểu thành phần>, <kiểu thành phần>) -> Bool

Đối số kiểu hàm này nhận một hàm/closure có cùng kiểu nhằm chỉ định cách so sánh hai thành phần trong mảng, hàm trả ra true nếu thứ tự sắp xếp là đúng, false nếu thứ tự sắp xếp không đúng. <kiểu thành phần> là kiểu thành phần của mảng. Với ví dụ này thì kiểu hàm sẽ là (String, String) -> Bool.

Chúng ta viết đoạn code sau để sắp xếp:

Hàm backwards định nghĩa cách sắp xếp giảm dần. Dòng lệnh 4 gọi hàm sorted và truyền tên hàm backwards vào.

Bây giờ, chúng ta thay hàm backwards bằng closure, thì đoạn code sẽ thành như sau:

Phần nằm trong cặp dấu ngoặc nhọn chính là closure và nó tuân theo cú pháp sau:

hàm backwards và closure này có phần xử lý giống nhau, khác nhau là một bên cài đặt riêng, một bên cài đặt nằm ngay trên dòng lệnh gọi hàm sorted. Thứ hai, là closure không cần tên hàm.

Em Yến cho phép trích kiểu dữ liệu từ ngữ cảnh - tức là ở ví dụ trên, bởi vì mảng names có kiểu thành phần là String, nên đương nhiên hai tham số đầu vào của closure phải có kiểu là String, còn kiểu trả về thì do hàm sorted đã quy định là Bool rồi. Do đó, lệnh gọi hàm sorted trên có thể viết đơn giản như sau:

Câu lệnh return có vẻ cũng không cần thiết, nên có thể viết thành:

Hoặc dùng cú pháp tên đối số dạng ngắn (shorthand argument name), câu lệnh trên có thể viết thành:

Hoặc dùng hàm toán tử (operator function), câu lệnh sẽ thành:

Cú pháp khai báo closure bên trong hàm có vẻ hơi khó hiểu (khác biệt). Do đó, có thể sử dụng dạng closure treo (trailing closure) như sau :

  • Closure còn được dùng để tính toán giá trị khởi tạo cho các thuộc tính (Xem phần Thuộc tính ở dưới để hiểu thuộc tính là gì).

Kỹ thuật liệt kê (enumeration)

Từ enumeration có nghĩa là liệt kê. Nhiều sách dạy lập trình dùng từ kiểu liệt kê để dịch từ này. Tuy nhiên, đây không phải là một kiểu vì bản thân nó đứng trơ trọi thì không phải là một kiểu. Nó chỉ là một cách thức để tạo ra các kiểu dữ liệu mới. Cho nên, mình tự ý đặt tên lại cho nó thành Kỹ thuật liệt kê. :)

Còn khi muốn nói đến enumeration type thì mới dịch là kiểu liệt kê và từ này để chỉ chung cho các kiểu được tạo ra bằng kỹ thuật liệt kê này.

  • Một kiểu liệt kê định nghĩa một kiểu chung cho một nhóm các giá trị có liên quan với nhau. Ví dụ, kiểu thứ để quản lý các thứ trong tuần, kiểu tháng để quản lý tên các tháng, kiểu nhóm thuốc để quản lý tên của các nhóm thuốc...

Cú pháp khai báo một kiểu liệt kê của em Yến có hơi khác một chút. Nó yêu cầu phải có từ case đứng trước mỗi dòng, tên đi theo sau case là giá trị thành phần của kiểu liệt kê.
Bạn có thể viết cách giá trị thành phần trên cùng một dòng, cách nhau bằng dấu phẩy.
  • Mỗi định nghĩa kiểu liệt kê sẽ tạo ra một kiểu dữ liệu mới. Do đó, bạn có thể dùng kiểu mới đó để khai báo biến, hằng, khai báo kiểu của tham số.

Khai báo biến directionToHead kiểu CompassPoint và nhận giá trị West.
  • Trong C, Objective C, C#, trình biên dịch gán cho mỗi tên trong kiểu liệt kê một giá trị số nguyên (0, 1, 2,...). Còn em Yến thì linh hoạt hơn, không cần thiết phải cung cấp một giá trị cho mỗi thành phần của kiểu liệt kê. Còn nếu muốn cung cấp một giá trị cho mỗi thành phần (em Yến gọi giá trị này là giá trị thô - raw value) thì giá trị đó có thể là một chuỗi, một ký tự, một số.
  • Em Yến còn linh hoạt hơn nữa khi hỗ trợ khả năng gắn thêm các giá trị vào các thành phần đã được định nghĩa của kiểu liệt kê. Có nghĩa là các giá trị này được gắn thêm vào kiểu liệt kê sau khi đã định nghĩa kiểu liệt kê. Xem ví dụ bên dưới.

Khai báo kiểu liệt kê Barcode với hai giá trị thành phần là UPCA và QRCode. Mỗi thành phần có thể gắn thêm các giá trị kiểu Int hoặc String.

Khai báo biến productBarcode và nó nhận giá trị là một barcode của một sản phẩm nào đó. Khá là thú vị với cách dùng này nhỉ !
  • Với em Yến, kiểu liệt kê còn có những tính năng mạnh như kiểu lớp, như : thêm các thuộc tính tính toán (computed property) - để cung cấp thông tin về giá trị hiện tại của kiểu liệt kê, thêm các hàm thành phần - để cung cấp chức năng cho giá trị mà kiểu liệt kê đang thể hiện, định nghĩa thêm cấu tử để cung cấp giá trị khởi tạo, có thể mở rộng tính năng của kiểu liệt kê đã có bằng cách áp dụng các protocol hoặc sử dụng kỹ thuật mở rộng (extension). Các đặc tính này sẽ được nói rõ bên dưới.

Class và structure

  • Lớp (class) và struct (structure) là các cấu trúc linh hoạt, cho phép bạn tổ chức ra các kiểu dữ liệu mới bằng cách thêm vào các thuộc tính, hàm chức năng,...
  • Em Yến không yêu cầu bạn tách phần khai báo và cài đặt ra hai file như đa số các ngôn ngữ lập trình (C++, Objective C).
  • Giống trong C#, Java, struct của em Yến có nhiều tính năng của lớp, như : thuộc tính, hàm thành phần, chỉ mục, cấu tử, cài đặt giao diện và sử dụng kỹ thuật mở rộng (extension). Tuy nhiên, struct của em Yến cũng giống các ngôn ngữ khác ở chỗ nó thuộc kiểu giá trị (Xem phần Kiểu dữ liệu để hiểu kiểu giá trị).
Thực tế thì các kiểu dữ liệu như Bool, Int, Double, string, array, dictionary của em Yến đều được tạo ra sử dụng struct.
  • Cú pháp khai báo lớp và struct khá giống nhau, ví dụ :

width, height, resolution,... không được gọi là biến thành phần, mà em Yến gọi là thuộc tính lưu trữ (stored property). Có tên này cũng vì mục đích của thuộc tính này là lưu trữ dữ liệu, tên này cũng để phân biệt với các loại thuộc tính khác. Sau này, mình sẽ gọi tắt thuộc tính lưu trữ bằng từ thuộc tính.

Với em Yến, bạn không cần phải tạo biến thành phần để lưu trữ dữ liệu, rồi sau đó mới tạo các thuộc tính để quy định cách truy xuất đến các thành phần. Bạn chỉ cần tạo thuộc tính, em Yến sẽ tự động lo phần biến thành phần cho bạn luôn rồi.

Trong C#, bạn có thể dùng thuộc tính tự động để tạo ra các thuộc tính mà không cần tạo biến thành phần. Các biến thành phần của các thuộc tính này cũng được tạo ngầm bên dưới cho bạn và nó là ẩn. Cho nên, bạn không thể truy xuất vào các biến thành phần này. Do đó, bạn không thể áp dụng các kiểm tra dữ liệu (business rules) cho các thuộc tính này được. Với em Yến, bạn có thể kiểm tra dữ liệu cho các thuộc tính này.

  • Các thuộc tính này có thể là biến hoặc hằng bằng cách dùng từ khoá var hoặc let, có thể khởi gán giá trị cho chúng và có thể truy xuất bằng cách dùng toán tử dấu chấm.

Như vậy, trong em Yến không có khái niệm khả năng truy xuất (private, public, protected) như đại đa số các ngôn ngữ lập trình. Thật ra là có những nó được bổ sung sau này và khác hơi nhiều so với khái niệm gốc. Xem phần Khả năng truy xuất ở dưới cùng bài này.
  • Các struct được cung cấp hàm cấu tử mặc định và có thể dùng tên các thuộc tính để xác định giá trị truyền vào thuộc về thuộc tính nào của struct, ví dụ:

  • Lớp là kiểu tham chiếu duy nhất trong em Yến. Tham chiếu ở đây cũng tương tự như con trỏ trong Pascal, C, C++, Objective C. Tuy nhiên, khi khai báo thì không cần dùng toán tử *.
Như vậy, em Yến đã gần với chị C# rồi.
  • Em Yến hỗ trợ toán tử === và !== để kiểm tra xem hai biến/hằng có tham chiếu hay không tham chiếu đến cùng một đối tượng.
Điều này đã loại bỏ sự nhập nhằng của phép toán == là để kiểm tra giống nhau về nội dung hay cùng tham chiếu đến một đối tượng trong các ngôn ngữ lập trình khác.

Thuộc tính (property)

  • Thuộc tính là nơi để lưu trữ dữ liệu trong các lớp, struct và kiểu liệt kê.
  • Em Yến hỗ trợ hai loại thuộc tính : thuộc tính lưu trữ (stored property), thuộc tính tính toán (computed property). 
  • Thuộc tính lưu trữ (viết tắt là thuộc tính), như đã nói ở trên, là nơi để lưu trữ dữ liệu bên trong các đối tượng của lớp, struct. Kiểu liệt kê không hỗ trợ thuộc tính lưu trữ. Thuộc tính lưu trữ có thể là biến hoặc hằng. Bạn có thể cung cấp giá trị mặc định cho các thuộc tính lưu trữ ngay trong định nghĩa của thuộc tính. Giá trị của thuộc tính có thể thay đổi trong quá trình khởi tạo (trong hàm cấu tử) ngay cả khi thuộc tính đó là hằng.
  • Còn thuộc tính tính toán là các thuộc tính trả ra các giá trị sử dụng các dữ liệu đã có (từ các thuộc tính lưu trữ, hoặc từ công thức tính toán). Thuộc tính này thực tế là không lưu trữ dữ liệu. Nó cung cấp một getter và một setter tuỳ chọn để lấy và gán giá trị cho các thuộc tính một cách gián tiếp.

center là thuộc tính tính toán, newValue là từ khoá mặc định sẽ nhận giá trị ở vế phải của phép gán cho thuộc tính tính toán (bạn có thể thay đổi tên newValue này thành bất kỳ tên nào bạn muốn).

Cú pháp của getter và setter gần như giống với C#. Tuy nhiên, chỉ có setter là tuỳ chọn, còn getter thì luôn luôn phải được cài đặt. Trong trường hợp setter không được cung cấp thì có thể viết lại thuộc tính tính toán như ví dụ sau:

  • Kiểu liệt kê có thể cài đặt các thuộc tính tính toán.
  • Em Yến còn cung cấp thêm một loại thuộc tính khác, gọi là thuộc tính lưu trữ lười (lazy stored property). Đây là thuộc tính lưu trữ mà giá trị khởi tạo của nó chỉ được tính toán khi thuộc tính đó được truy xuất lần đầu tiên. Thuộc tính loại này phù hợp cho các thuộc tính chứa các đối tượng phụ thuộc vào các nhân tố bên ngoài, ví dụ : đối tượng chứa liên kết mạng, đối tượng đọc ghi file,... Để sử dụng thì thêm từ khoá lazy như ví dụ dưới đây.

  • Giá trị khởi tạo của thuộc tính lưu trữ có thể là kết quả của một hàm hoặc một closure.

Thuộc tính boardColors được khởi gán bằng closure.

Cặp dấu mở ngoặc, đóng ngoặc ở cuối closure cần phải có để trình biên dịch thực thi ngay closure khi được gán. Nếu không có boardColors sẽ có kiểu hàm và nhận giá trị là closure đó.

Bên trong closure hoặc hàm dạng này, bạn không thể truy xuất vào các thuộc tính khác, thuộc tính self, cũng như các hàm thành phần.

Bộ theo dõi thuộc tính (property observer)

  • Bộ theo dõi thuộc tính theo dõi và phản ứng lại những thay đổi trong giá trị của các thuộc tính. Bộ theo dõi sẽ được thực thi mỗi khi giá trị thuộc tính được gán, thậm chí cả khi giá trị mới giống giá trị cũ.
Đây là tính năng khá đặc sắc. Nó giống với các trigger trong cơ sở dữ liệu. Nó cũng giống với event (sự kiện) và delegate trong nhiều ngôn ngữ lập trình (C#, Java). Tuy nhiên, bộ theo dõi này chỉ hoạt động cho thuộc tính, event thì rộng hơn theo dõi cả những tương tác trên hệ thống. Thêm nữa, bộ theo dõi là tự động, còn event là phải tự phát sinh.
  • Bạn có thể thêm bộ theo dõi vào các thuộc tính lưu trữ, vào bất kỳ các thuộc tính nào (kể cả thuộc tính tính toán) được thừa kế từ lớp cha bằng cách nạp chồng thuộc tính ở lớp con. Xem phần Thừa kế ở dưới để hiểu về thừa kế và nạp chồng.
  • Em Yến cung cấp hai loại bộ theo dõi là: willSet và didSet. willSet được gọi trước khi giá trị được gán, didSet được gọi sau khi giá trị được gán.

  • Nhờ bộ theo dõi này, bạn có thể kiểm tra dữ liệu được gán vào cho các thuộc tính. Vì vậy, thuộc tính của em Yến mạnh mẽ hơn so với thuộc tính trong C# khá nhiều.

Thuộc tính cấp kiểu (type property)

Trong các ngôn ngữ như C++, C#, Objective C, chỉ hỗ trợ tính năng này trong lớp, nên người ta còn gọi chúng là thành phần cấp lớp. Em Yến thì hỗ trợ cho cả struct và kiểu liệt kê, nên không thể dùng từ thuộc tính cấp lớp được mà phải dịch là thuộc tính cấp kiểu.

Từ kiểu ở đây là viết tắt của từ kiểu dữ liệu.

  • Trong lập trình hướng đối tượng, đối tượng là đối tượng rồi và các kiểu cũng cần phải được xem như một đối tượng. Đối tượng có thuộc tính, hàm thành phần, thì kiểu cũng phải có thuộc tính và hàm thành phần của kiểu. Người ta gọi là thuộc tính cấp kiểu và hàm thành phần cấp kiểu.

Từng đối tượng có thông tin riêng của đối tượng thì kiểu dữ liệu cũng có thông tin riêng của kiểu.

Trong C++, C#, các thành phần được tạo ra với từ khoá static chính là các thành phần cấp kiểu. Muốn truy xuất các thành phần này thì phải thông qua kiểu, tức là viết <tên kiểu> + "." + <tên thành phần>. Ví dụ : Console.Write();

  • Các thuộc tính cấp kiểu được dùng để cung cấp các giá trị mà tất cả các đối tượng của kiểu đó đều có thể cần dùng. Ví dụ: số đối tượng đã được tạo ra từ kiểu đó, giá trị max, min mà các đối tượng chỉ thuộc vào đó,...
  • Đối với các kiểu giá trị (như struct, kiểu liệt kê) bạn có thể định nghĩa cả thuộc tính lưu trữ và tính toán cấp kiểu. Còn với kiểu lớp thì bạn chỉ có thể định nghĩa thuộc tính tính toán cấp kiểu.
  • Thuộc tính lưu trữ cấp kiểu bắt buộc phải có có giá trị khởi tạo, bởi vì không có hàm cấu tử cấp kiểu để khởi gán cho thuộc tính cấp kiểu.
  • Cú pháp khai báo thuộc tính cấp kiểu như sau:

Dùng từ khoá static với struct và kiểu liệt kê, dùng từ khoá class với lớp.

Mình cũng không hiểu vì sao phải dùng hai từ khoá khác nhau.

Hàm thành phần (method)

  • Hàm thành phần là các hàm thuộc về một kiểu cụ thể nào đó. Với em Yến, kiểu ở đây có thể là kiểu lớp, struct hoặc kiểu liệt kê.
Việc có thể định nghĩa các hàm thành phần trong kiểu liệt kê là khác biệt rất lớn so với C, Objective C, C# và Java. C#, Java chỉ hỗ trợ thêm hàm thành phần trong struct.

Cú pháp khai báo giống hàm (Xem phần Hàm ở trên), chỉ có vị trí thì nằm bên trong phần khai báo của lớp hoặc struct hoặc kiểu liệt kê.
  • Giống như hàm, các hàm thành phần vẫn có tham số cục bộ và tham số ngoài. Tuy nhiên, tham số ngoài lại hoạt động hơi khác so với hàm và cách hoạt động của hàm thành phần của Objective C. Cụ thể, trong Objective C, tên hàm thành phần cũng chính là tên tham số đầu tiên của hàm kết hợp với 1 giới từ (with, for, by). Ví dụ với câu lệnh [counter incrementBy: 5 numberOfTimes: 3]; thì hàm thành phần là incrementBy. Việc sử dụng giới từ ở đây cho phép lời gọi hàm giống như một câu văn. Do đó, theo mặc định, em Yến cung cấp tham số ngoài cho tất cả các tham số ngoại trừ tham số đầu tiên. Nếu bạn không cung cấp tham số ngoài thì tên của tham số cục bộ sẽ được sử dụng làm tên tham số ngoài.

Lớp counter có hàm thành phần incrementBy. Khi gọi hàm thành phần thì câu lệnh như sau:

Chỉ có tham số thứ hai là có thể dùng tham số ngoài.

Câu lệnh trông giống như một câu văn. Nhưng dấu chấm và dấu đóng, mở ngoặc có vẻ làm hỏng cú pháp câu văn đó.

  • Structure và enum là các kiểu giá trị, nên mặc định em Yến không cho phép thay đổi dữ liệu của các thuộc tính của nó từ các hàm thành phần. Còn nếu muốn thay đổi thì bạn phải thêm từ khoá mutating cho định nghĩa hàm đó.

Ngoài ra, còn có thể thay đổi cả bản thân đối tượng thông qua thuộc tính self (giống con trỏ this của C#).

Thuộc tính self này dùng được cho cả struct và kiểu liệt kê. Con trỏ this thì chỉ dùng cho kiểu lớp.
  • Giống các ngôn ngữ khác, em Yến cũng cung cấp các hàm cấp kiểu. Bạn khai báo hàm cấp kiểu cho lớp thì dùng từ khoá class và khai báo cho struct và kiểu liệt kê thì dùng từ khoá static.
Tương tự thuộc tính cấp kiểu, em Yến không chỉ cung cấp hàm cấp kiểu cho kiểu lớp, mà cho cả struct và kiểu liệt kê.
  • Vẫn có thể sử dụng thuộc tính self trong hàm cấp kiểu. Tuy nhiên, lúc này, nó tham chiếu đến chính bản thân kiểu đó.

Chỉ mục (subscript)

  • Kiểu lớp, struct và kiểu liệt kê có thể định nghĩa các chỉ mục (subscript). Giống như thuộc tính chỉ mục trong C#.
Trong các ngôn ngữ mình biết thì chỉ có C# và em Yến là hỗ trợ cái này.
  • Cú pháp của một chỉ mục khá đơn giản, như sau:

Chỉ mục cũng hỗ trợ getter và setter giống như thuộc tính tính toán. Nếu muốn chỉ mục là chỉ đọc thì có thể loại bỏ phần setter giống như làm với thuộc tính tính toán chỉ đọc.
  • Giống C#, bạn có thể khai báo nhiều chỉ mục cho một kiểu.
  • Bạn cũng không bị giới hạn về số lượng tham số, kiểu của tham số, cũng như kiểu trả về khi định nghĩa chỉ mục. Tuy nhiên, bạn không thể sử dụng tham số in-out và cung cấp giá trị mặc định cho tham số.

Thừa kế

  • Thừa kế là khả năng một lớp thừa kế các hàm thành phần, thuộc tính và các đặc tính khác từ một lớp khác. Lớp thừa kế gọi là lớp con, lớp được thừa kế gọi là lớp cha.
Thừa kế chỉ áp dụng cho lớp và nó tạo sự khác biệt giữa lớp với các kiểu khác.
  • Em Yến không cung cấp một superbase class (giống như lớp Object của C#) và không bắt buộc lớp của bạn phải thừa kế từ lớp khác.

Lớp Vehicle không có thừa kế từ lớp nào. Em Yến gọi các lớp không thừa kế từ lớp nào là lớp cơ sở (base class).

Thuộc tính tính toán description ở đây có chức năng giống hàm ToString của C#, nhưng nó không được tự động gọi như ToString. Cách làm việc này được sao chép từ mẫu lập trình của Objective C.

  • Cú pháp thừa kế thì vẫn giống với các ngôn ngữ hướng đối tượng khác, nhưng không cần các toán tử truy xuất (public, private, protected,...) như C++, C#, Java.
  • Lớp con cũng có thể nạp chồng hàm thành phần, hàm cấp lớp, thuộc tính thành phần, thuộc tính cấp lớp, chỉ mục của lớp cha bằng cách sử dụng từ khoá override.
Từ khoá override ở đây giúp người lập trình tránh các lỗi không mong muốn, như : sai định nghĩa của thành phần cần nạp chồng.

Ví dụ nạp chồng hàm thành phần makeNoise của Vehicle.

Cấu tử

  • Khởi tạo (Initialization) là tiến trình chuẩn bị một đối tượng của lớp, struct, kiểu liệt kê để sử dụng. Tiến trình này bao gồm việc gán giá trị khởi tạo cho mỗi thuộc tính lưu trữ của kiểu và thực thi các thiết lập hoặc tiến trình khởi tạo khác cần thiết trước khi một đối tượng mới sẵn sàng để sử dụng. Bạn cài đặt tiến trình này trong một hàm đặc biệt, gọi là cấu tử. Hàm cấu tử của em Yến có tên là init, chứ không phải là cùng tên với tên kiểu như các ngôn ngữ hướng đối tượng khác.
Cấu tử của em Yến khác so với Objective C ở chỗ nó không cần câu lệnh return trả ra một giá trị (giống với C#). Nhiệm vụ chủ yếu của nó là đảm bảo các đối tượng mới được khởi tạo trước chúng được sử dụng.
  • Em Yến yêu cầu tất cả các thuộc tính lưu trữ của kiểu lớp và struct đều phải được khởi gán giá trị ngay khi một đối tượng mới của kiểu đó được tạo ra. Bạn có thể thực hiện khởi gán trong hàm cấu tử hoặc thông qua gán giá trị mặc định ngay khi khai báo thuộc tính.

Trong struct Fahrenheit có thuộc tính lưu trữ temperature, nên trong hàm cấu tử (init), chúng ta phải khởi gán cho thuộc tính này.

Việc gán giá trị mặc định và khởi gán giá trị bên trong hàm cấu tử không làm phát sinh lời gọi đến bộ theo dõi của thuộc tính.

Nếu thuộc tính lưu trữ thuộc kiểu optional thì có thể không cần khởi gán giá trị.

Bạn có thể thay đổi giá trị của các thuộc tính hằng trong quá trình khởi tạo của lớp khai báo thuộc tính hằng đó. Các lớp thừa kế không thể thay đổi giá trị của thuộc tính hằng của lớp cha.

  • Cấu tử cũng là một hàm, nên em Yến cũng hỗ trợ tham số ngoài cho cấu tử. Mặc định, tham số ngoài sẽ được tạo ra tự động cho tất cả các tham số.

struct Color khai báo hai cấu tử: một nhận ba tham số red, green, blue; một là tham số white. Em Yến tự động tạo ra các tham số ngoài cùng tên với tham số cục bộ.

Tham số ngoài là bắt buộc phải dùng khi truyền tham số cho cấu tử, nếu không có thì sẽ phát sinh lỗi trong quá trình biên dịch.
  • Một hàm cấu tử có thể gọi một cấu tử khác để thực hiện một phần của tiến trình khởi tạo đối tượng.

Hàm cấu tử init(center, size) gọi hàm cấu tử init(origin, size) sử dụng thuộc tính self.
  • Với lớp, do có thừa kế, nên các lớp còn có trách nhiệm phải đảm bảo các thuộc tính được thừa kế phải được khởi gán. Do đó, trong hàm cấu tử của lớp con phải gọi hàm cấu tử của lớp cha. Tuy nhiên, để tránh những lỗi không lường trước, em Yến chia các hàm cấu tử thành hai loại (designated init và convenience init - chưa biết dịch thế nào) và yêu cầu việc gọi hàm cấu tử của nhau phải tuân theo các quy định.
  • Designated init là các hàm cấu tử chính cho một lớp. Nó đảm bảo hai việc : 1. khởi tạo tất cả các thuộc tính do lớp đó cung cấp, 2. gọi một hàm cấu tử thích hợp của lớp cha (một designated init của lớp cha). Một lớp có thể có nhiều và ít nhất một designated init.
  • Convenience init là các cấu tử phụ, bổ trợ - thông thường vài thuộc tính sẽ giữ nguyên giá trị mặc định. Convenience init chỉ gọi một designated init hoặc convenience init của cùng lớp. Convenience init có thể có hoặc không.
  • Quá trình khởi tạo đối tượng của em Yến dùng hai pha như trong Objective C. Trong pha thứ nhất, mỗi thuộc tính được gán giá trị khởi tạo bởi lớp cung cấp thuộc tính đó. Sau đó, pha thứ hai, quá trình khởi tạo mới cho phép thay đổi các giá trị này (giống cách làm của C#). Cách làm này an toàn hơn, nó ngăn ngừa việc sử dụng biến trước khi cấp phát. Điểm khác biệt với Objective C, là em Yến linh hoạt hơn bằng cách thay vì gán giá trị mặc định của kiểu dữ liệu của thuộc tính thì nó gán bằng giá trị tuỳ chọn luôn.
  • Các lớp con của em Yến mặc định không thừa kế các cấu tử từ lớp cha. Điều này nhằm ngăn chặn việc thừa kế một cấu tử khởi tạo thiếu các thuộc tính của lớp con. Bạn bắt buộc phải nạp chồng nếu muốn cài đặt các tuỳ chọn.

Lớp Bicycle phải nạp chồng hàm cấu tử của Vehicle để thiết lập giá trị thuộc tính numberOfWheels là 2.

Tuy nhiên, lớp con sẽ tự động thừa kế tất cả các hàm cấu tử của lớp cha khi tất cả các thuộc tính đều được cung cấp giá trị mặc định và :

    1. Nếu lớp con không định nghĩa designated init nào, nó sẽ tự động thừa kế tất cả các hàm cấu tử của lớp cha
    2. Nếu lớp con nạp chồng tất cả các designated init của lớp cha thì nó sẽ thừa kế tất cả các convenience init của lớp cha.
Việc ngăn chặn và tự động thừa kế này cho phép em Yến vừa an toàn vừa linh động, nhưng sẽ hơi khó hiểu một xí.
  • Muốn ép buộc lớp con phải nạp chồng hàm cấu tử nào thì bạn thêm từ khoá required ở trước khai báo hàm cấu tử đó.

Huỷ tử

  • Huỷ tử sẽ được gọi ngay khi một đối tượng của lớp bị huỷ. Các hàm huỷ tử chỉ có trên các lớp và cài đặt hàm deinit để cài đặt tiến trình huỷ tử.

  • Em Yến quản lý bộ nhớ các đối tượng thông qua ARC (automatic reference counting - bộ đếm tham chiếu tự động).
  • Với em Yến, hàm huỷ tử sẽ được tự động thực hiện khi có một đối tượng cần bị huỷ bộ nhớ. Bạn không được phép tự gọi hàm huỷ tử. Tuy nhiên, bạn vẫn cần có hàm huỷ tử để làm một số việc, ví dụ : đóng file, ngắt kết nối đến cơ sở dữ liệu,… Hàm huỷ tử của lớp cha sẽ được lớp con thừa kế và sẽ tự động được gọi ở cuối phần cài đặt của hàm huỷ tử của lớp con. Hàm huỷ tử của lớp cha luôn được gọi ngay cả khi lớp con không cài đặt hàm huỷ tử nào.

Objective C và C# đều hỗ trợ cả không tự động và tự động cho quá trình huỷ tử trên.

C, C++ thì hoàn toàn giao cho người lập trình quản lý.

ARC (automatic reference counting - bộ đếm tham chiếu tự động)

  • Giống Objective C, em Yến cũng dùng ARC để theo dõi và quản lý việc sử dụng bộ nhớ của ứng dụng. ARC tự động giải phóng bộ nhớ của các đối tượng lớp khi những đối tượng này là không còn cần thiết. Việc huỷ bộ nhớ của các đối tượng chỉ xảy ra khi không còn 1 thuộc tính, biến, hằng nào còn tham chiếu đến đối tượng đó. Việc kiểm tra xem có còn 1 thuộc tính, biến, hằng nào đó tham chiếu đến đối tượng thì có nhiều cách. Em Yến dùng cách Objective C đã dùng khá hiệu quả là sử dụng một biến đếm tham chiếu (reference counting). Mỗi khi một đối tượng được tạo ra thì biến đếm cho đối tượng đó sẽ được gán bằng 1. Nếu trong quá trình hoạt động lại có một biến khác nhận địa chỉ tham chiếu đến đối tượng đó (thông qua phép gán) thì biến đếm của đối tượng đó sẽ tăng lên 2. Khi một biến nào đó không tham chiếu đến đối tượng đó nữa (gán giá trị nil cho biến) thì biến đếm tham chiếu của đối tượng đó sẽ giảm đi 1. Cho đến khi nào biến đếm về bằng 0 thì ARC sẽ chạy quá trình huỷ tử. Hình dưới đây minh hoạ quy trình này một cách đơn giản dễ hiểu nhất (alloc - lệnh cấp phát bộ nhớ, retain - lệnh tăng đếm lên 1, release - lệnh giải phóng).

Hình trên minh hoạ quy trình làm việc của ARC. alloc - lệnh cấp phát bộ nhớ, retain - lệnh tăng đếm lên 1, release - lệnh yêu cầu giải phóng bộ nhớ. Đây là các lệnh của Objective C.

ARC chỉ làm việc với các đối tượng kiểu lớp.

Em Yến dùng cách này nhưng không dùng đống lệnh alloc, retain, release, mà toàn bộ việc này sẽ được thực hiện tự động. Khi bạn muốn release thì chỉ cần gán nil cho biến tham chiếu đến đối tượng muốn giải phóng. Cách làm tự động này tương tự Trình gom rác của .NET.

  • Với ARC, khi bạn gán một đối tượng cho một biến, hằng, thuộc tính thì biến, hằng, thuộc tính sẽ tạo ra một tham chiếu mạnh (strong reference) đến đối tượng đó.
  • Một vấn đề đối với các hệ thống dọn dẹp bộ nhớ tự động của các nền tảng lập trình là vấn đề tham chiếu vòng (reference cycle). Đó là khi hai đối tượng tham chiếu lẫn nhau. Lúc đó, trình dọn dẹp bộ nhớ tự động sẽ không thể giải phóng bộ nhớ của cả hai đối tượng này.

Em Yến gọi tham chiếu vòng là tham chiếu vòng mạnh (strong reference cycle).

Theo như mình tìm hiểu được thì C# cũng có cách giải quyết vấn đề này, nhưng theo mình thì vẫn chưa ổn lắm. Cách của Objective C và em Yến là tốt hơn.

  • Em Yến, cung cấp thêm hai loại tham chiếu là tham chiếu yếu (weak reference) và tham chiếu không sở hữu (unowned reference). Và luật mới là ARC sẽ chỉ xoá bộ nhớ một đối tượng khi số tham chiếu mạnh (strong reference) là bằng 0.
  • Sử dụng tham chiếu yếu khi bạn biết rằng trong vòng đời của biến đó có thể có lúc nó không tham chiếu đến đối tượng nào (nil).

  • Sử dụng tham chiếu không sở hữu khi bạn biết rằng trong vòng đời của nó, nó sẽ không bao giờ nil một khi nó đã được khởi tạo.

Objective C chỉ hổ trợ weak reference.

Theo như mình hiểu, thì weak reference được dùng cho các đối tượng mà có 1 biến tham chiếu vào, còn unowned reference (unowned - không có ai làm chủ) được dùng cho các đối tượng mà không có biến nào tham chiếu vào, ngoại trừ để các đối tượng khác tham chiếu.

  • Với các lớp có dùng closure để khởi tạo các thuộc tính thì cũng có thể gặp trường hợp trên. Lúc đó, có thể dùng tham chiếu không sở hữu để giải quyết.

Chuyển đổi kiểu (type casting)

  • Em Yến cung cấp hai toán tử is và as tương tự trong C# để kiểm tra và ép kiểu dữ liệu. Ngoài ra còn có thêm as? cho kiểu optional.
  • Em Yến còn cung cấp thêm hai kiểu AnyObject và Any cho các kiểu chưa xác định. AnyObject thể hiện cho bất kỳ một kiểu lớp nào. Any thể hiện cho tất cả các kiểu, kể cả kiểu hàm.

Kỹ thuật mở rộng (Extension)

  • Kỹ thuật mở rộng cho phép bổ sung thêm các chức năng cho các lớp, struct, kiểu liệt kê đã có. Việc bổ sung chức năng này là không cần truy cập vào mã nguồn của lớp, struct, kiểu liệt kê đã có đó.
Tương tự category của Objective C. Tuy nhiên, trong em Yến, các extension không có tên. C# cũng có khả năng tương tự, nhưng chỉ bổ sung hàm thành phần, không thêm vào được cho cùng lớp, cách cài đặt và sử dụng không tự nhiên.
  • Kỹ thuật mở rộng của em Yến cho phép: thêm thuộc tính tính toán, hàm thành phần, hàm cấp lớp, hàm huỷ tử, chỉ mục, kiểu mới lồng bên trong, cài đặt một giao thức (protocol).
  • Dưới đây là một extension mở rộng lớp Double và sử dụng lớp Double mới.
  • Một ví dụ mở rộng lớp Int và sử dụng

  • Một ví dụ mở rộng lớp Character.

Giao thức (protocol)

  • Giao thức định nghĩa nguyên mẫu của các hàm thành phần, thuộc tính, chỉ mục và những yêu cầu khác cho một nhiệm vụ cụ thể nào đó. Giao thức không cung cấp cài đặt cho các yêu cầu đó, nó chỉ mô tả một cài đặt sẽ trông như thế nào. Sau đó, các lớp, struct, kiểu liệt kê có thể áp dụng (adopt) giao thức đó để cung cấp một cài đặt cho những yêu cầu của giao thức. Các kiểu thoả mãn những yêu cầu của một giao thức thì được gọi là phù hợp (conform) với giao thức đó.

Protocol chính là Interface trong C++, C#, Java. Do xuất phát từ từ interface, nên người ta dùng thuật ngữ giao diện để gọi kỹ thuật này. Còn theo mình thì bản thân chữ giao diện nó đã không thể hiện đúng bản chất của kỹ thuật này. Thứ nhất, bởi vì bản thân một lớp cũng đã cung cấp một giao diện cho người dùng của lớp đó, nên nếu ở đây cũng dùng từ giao diện thì bị trùng. Thứ hai, những gì chúng ta định nghĩa giống các giao thức mà các lớp phải tuân theo. Có nghĩa là nó bao hàm luôn cả cách thức làm việc, chứ không chỉ cái nguyên mẫu hàm. Do đó, theo tôi chữ giao thức thể hiện đúng kỹ thuật này hơn cả.

Từ nguyên mẫu xuất phát từ từ nguyên mẫu hàm dùng trong C, C++. Nguyễn mẫu hàm chính là dòng khai báo tên hàm, các tham số và kiểu trả về của hàm.

    • Một giao thức có thể yêu cầu các kiểu phù hợp với nó phải có các hàm thành phần, thuộc tính, hàm cấp kiểu, toán tử và chỉ mục cụ thể nào đó.
    • Cú pháp khai báo giao thức như sau :

    Một kiểu muốn áp dụng một giao thức thì viết theo cú pháp sau :

    Nếu lớp có thừa kế từ lớp khác thì viết tên lớp cha trước các giao thức.

    Em Yến cho phép lớp, structure, kiểu liệt kê đều có thể áp dụng giao thức.

    • Ví dụ:

    Giao thức FullyNamed yêu cầu các lớp áp dụng giao thức này phải có một thuộc tính trả ra tên đầy đủ (full name).

    Lớp Starship áp dụng giao thức FullyNamed đã cài đặt thuộc tính fullName để trả ra tên đầy đủ.
    • Giao thức cũng là một kiểu dữ liệu, nên có thể khai báo biến, hằng, thuộc tính có kiểu giao thức, có thể dùng kiểu giao thức là kiểu của tham số hay kiểu trả ra của hàm, hàm thành phần, cấu tử.
    • Em Yến còn cho phép các giao thức thừa kế từ giao thức khác và cho phép tạo ra giao thức bằng cách kết hợp nhiều giao thức vào với nhau (gọi là giao thức kết hợp - protocol composition).
    • Dùng toán tử is, as, as? để kiểm tra và ép kiểu về giao thức.

    Delegation

    • Trong C# và Java, người ta cung cấp hai kiểu dữ liệu là delegation và event. Delegation giống như "trao trách nhiệm cho ai đó". Ví dụ, bạn giao trách nhiệm cho một biến nào đó có khả năng thụ lý (gọi) một hàm nào đó. Vậy thì từ biến đó ta có thể gọi thực hiện hàm đó. Event dịch ra là sự kiện. Đây là kiểu mở rộng của delegation. Có thể diễn dịch kiểu này thành "khi có điều gì xảy ra thì gọi hàm nào để xử lý". Ở đây, có thể có nhiều hàm cùng xử lý khi một sự kiện xảy ra, nên event được tạo ra để hổ trợ cho điều đó.
    • Còn với em Yến, delegation chỉ là một mẫu thiết kê (design pattern) cho phép một lớp, struct uỷ thác một số trách nhiệm của nó cho một kiểu khác. Mẫu thiết kế này được cài đặt bằng cách định nghĩa một giao thức mà nó đóng gói những trách nhiệm được uỷ thác đó. Một kiểu áp dụng giao thức đó sẽ phải đảm bảo cung cấp những chức năng được uỷ thác đó. Delegation được sử dụng để hồi đáp lại một hành vi cụ thể hoặc để trích rút dữ liệu từ bên ngoài mà không cần biết kiểu của nguồn dữ liệu.
    Em Yến không tạo ra một kiểu riêng cho delegation, mà dùng ngay luôn kiểu giao thức.
    • Ví dụ:

    Khai báo một delegation (một protocol được dùng làm delegation).

    Lớp DiceGameTracker áp dụng delegation trên và cài đặt ba hàm để xử lý cho ba sự kiện game bắt đầu, game chơi một lượt và game kết thúc.

    Lớp SnakesAndLadders khai báo một biến kiểu delegation. Hàm play dùng biến này để gọi các hàm xử lý các sự kiện tương ứng.
    • Em Yến hầu như không nhắc gì đến event, nhưng có đưa ra cách dùng một mảng các delegation. 

    Khuôn mẫu (Generic)

    • Khuôn mẫu cho phép bạn tạo ra các hàm, các kiểu có thể làm việc với mọi kiểu dữ liệu. Ví dụ: bạn viết 1 hàm tính tổng duy nhất nhưng bạn có thể truyền cho nó kiểu dữ liệu số nguyên, số thực gì cũng được. Hoặc bạn viết một lớp Stack nhận các thành phần của tất cả các kiểu dữ liệu.

    Trong C#, lớp List<T> là một generic.

    Khuôn mẫu của em Yến mạnh mẽ hơn khuôn mẫu của C++ và generic của C#.

    Hầu hết thư viện chuẩn của em Yến được viết sử dụng khuôn mẫu. Ví dụ: array và dictionary có thể dùng với kiểu gì cũng được.

    • Ví dụ khuôn mẫu hàm :

    Đây là ví dụ thể hiện phải viết nhiều hàm để thực hiện công việc hoán đổi giá trị hai biến, mỗi hàm xử lý cho một kiểu giá trị khác nhau.

    Dùng khuôn mẫu với cú pháp tham số kiểu (type parameter) <T>, ta chỉ cần cài đặt một hàm duy nhất:

    • Em Yến cho phép dùng nhiều tham số kiểu và đặt tên cho tham số kiểu để dễ phân biệt.
    • Ví dụ khuôn mẫu kiểu (kiểu generic tức là khuôn mẫu lớp, khuôn mẫu struct, khuôn mẫu kiểu liệt kê):

    • Em Yến còn cho phép thêm các hạn chế kiểu (type constraint) vào khuôn mẫu để các khuôn mẫu kiểu, khuôn mẫu hàm hoạt động tốt hơn. Các hạn chế kiểu xác định các tham số kiểu (type parameter) phải thừa kế từ một kiểu nào đó hoặc áp dụng một giao thức nào đó.

    <T: Equatable> thể hiện kiểu dùng với hàm findIndex phải là kiểu có áp dụng giao thức Equatable.
    • Còn có thể thêm cả mệnh đề where (điều kiện) vào trong phần khai báo hạn chế kiểu.

    Kiểu kết hợp (associated type) trong giao thức

    • Đôi khi, khi khai báo một giao thức bạn cần thêm một hoặc vài kiểu kết hợp. Đó là các kiểu chưa xác định cụ thể khi khai báo giao thức. Đến khi giao thức được áp dụng thì mới gán kiểu cụ thể vào. Em Yến cung cấp các bí danh cho các kiểu đó. Bí danh này sẽ được dùng trong phạm vi của giao thức.
    • Cú pháp khai báo tương tự như sau:

    ItemType là bí danh cho kiểu được dùng trong giao thức.

    Áp dụng giao thức Container:

    Nạp chồng toán tử

    • Chỉ có lớp và struct là có thể nạp chồng toán tử.
    Với em Yến, struct cũng đã có thể nạp chồng toán tử.
    • Các toán tử được nạp chồng bằng các hàm toàn cục. Không bị phân tán cái trong lớp, cái ngoài lớp như C++ và C#.

    Nạp chồng toán tử + cho struct Vector2D.

    • Sử dụng từ khoá prefix và postfix để quy định vị trí xuất hiện trước hay sau của toán tử với toán hạng.
    • Em Yến cho phép tạo ra các toán tử mới. Lệnh sau đây khai báo toán tử +++:

    Ban đầu thì nó chưa có ý nghĩa gì, bạn phải nạp chồng để cung cấp ý nghĩa cho nó. Ví dụ:

    • Khi tạo ra toán tử mới, bạn có thể thiết lập thứ tự ưu tiên cho toán tử đó:

    Khả năng truy xuất

    Khả năng truy xuất chỉ mới được thêm vào với bản beta 5 của ngôn ngữ lập trình này.
    • Khả năng truy xuất giới hạn truy xuất vào các phần trong mã nguồn của bạn từ những mã nguồn trong file khác hoặc module khác.
    • Bạn có thể chỉ định cấp độ truy xuất cho các kiểu cụ thể (lớp, struct, kiểu liệt kê), cũng như cho thuộc tính, hàm thành phần, cấu tử, chỉ mục của kiểu đó.
    • Thực tế, khi bạn tạo ra một app riêng lẻ thì không cần phải chỉ định khả năng truy xuất làm gì. Nên em Yến cung cấp khả năng truy xuất mặc định (internal) và bạn không cần phải viết rõ nó ra trong mã nguồn.
    • Em Yến chia cấp độ mã nguồn của bạn ra hai cấp độ là module và tập tin mã nguồn.
      • Một module là một đơn vị riêng lẻ của mã nguồn được phân phối, ví dụ: một framework hay một ứng dụng. Nó được biên dịch và chuyển giao như một đơn vị riêng lẻ và có thể được sử dụng bởi module khác.
      • Một tập tin mã nguồn là một tập tin mã nguồn Swift riêng lẻ trong một module.
    • Từ đó, em Yến cung cấp 3 cấp độ truy xuất: public, internal và private. Trong đó, private chỉ cho phép truy xuất trong một tập tin mã nguồn và internal chỉ cho phép truy xuất trong một module.

    Kết luận

    Có thể nói em Yến như một cô gái lai. Em ấy sở hữu động cơ mạnh mẽ, hiệu quả và cú pháp gọi hàm dạng câu văn của Objective C; sử dụng các cú pháp câu lệnh ít khác người của C#, Java; bổ sung và cải tiến các cú pháp linh động, hiệu năng cao và hiện đại của các ngôn ngữ dạng script (Python,…); và cuối cùng là gia tăng thêm tính an toàn cho câu lệnh. Và giờ, em ấy sở hữu một thân hình thật quyến rũ đủ để mê hoặc tất cả các lập trình viên.

    Comments