Lập trình · Ngôn ngữ lập trình
Hướng dẫn học lập trình F# cơ bản cho người mới
Trả lời nhanh
F# là ngôn ngữ ưu tiên hàm, kiểu tĩnh, dành cho .NET. Nó suy luận hầu hết kiểu giúp bạn, dùng thụt lề thay cho dấu ngoặc nhọn, và coi dữ liệu bất biến, khớp mẫu cùng toán tử pipeline |> là cách viết mã thông thường, đồng thời vẫn truy cập đầy đủ các thư viện .NET. Cài .NET SDK và chạy script bằng dotnet fsi hello.fsx.
F# là gì và ai dùng nó
F# được Don Syme thiết kế tại Microsoft Research và nay là ngôn ngữ mã nguồn mở do Microsoft và cộng đồng F# Software Foundation duy trì. Nó thuộc họ ngôn ngữ ML (như OCaml) và chạy trên .NET trên Windows, macOS và Linux.
Các nhóm dùng F# cho mô hình tài chính, phân tích dữ liệu, logic nghiệp vụ nặng về miền, backend web và viết script. Vì biên dịch ra cùng ngôn ngữ trung gian .NET như C#, một dự án F# có thể dùng bất kỳ gói NuGet nào và có thể được gọi từ mã C#.
Cài F# và chạy Hello, world
Cài .NET SDK từ Microsoft. F# đã đi kèm, nên không cần cài riêng. Để thử nhanh, viết một tệp script có đuôi .fsx và chạy bằng F# Interactive (dotnet fsi).
// hello.fsx
printfn "Hello, world!"dotnet fsi hello.fsx
# Hello, world!
dotnet new console -lang "F#" -o HelloApp
cd HelloApp
dotnet runNhóm lệnh thứ hai tạo một dự án console đầy đủ với tệp .fsproj và Program.fs. Trong dự án, thứ tự các tệp liệt kê trong .fsproj rất quan trọng: một tệp chỉ dùng được mã từ các tệp phía trên nó.
Giá trị và các kiểu dữ liệu cơ bản
let gắn một tên với một giá trị. Các binding là bất biến trừ khi bạn đánh dấu mutable, và khi đó bạn đổi giá trị bằng <-, không phải =.
| Kiểu | Ví dụ | Ghi chú |
|---|---|---|
| int | 42 | số nguyên 32 bit |
| float | 3.14 | số thực dấu phẩy động 64 bit (một double của .NET) |
| decimal | 19.99m | số thập phân chính xác, phù hợp cho tiền |
| string | "hello" | chuỗi .NET bất biến |
| char | 'a' | một ký tự đơn |
| bool | true, false | giá trị logic |
| unit | () | "không có giá trị", giống void |
let name = "Ana"
let age = 30
let mutable count = 0
count <- count + 1
printfn "%s is %d, count = %d" name age count
// Ana is 30, count = 1
let total = float age + 0.5 // explicit conversion neededF# không bao giờ chuyển đổi số ngầm: 1 + 2.0 là lỗi biên dịch. Hãy chuyển đổi bằng các hàm như float, int và string.
Điều khiển luồng và hàm
if là biểu thức trả về giá trị, nên cả hai nhánh phải cùng kiểu. Vòng lặp có tồn tại (for, while), nhưng phần lớn mã dùng các hàm trên collection.
let sign n =
if n > 0 then "positive"
elif n < 0 then "negative"
else "zero"
for i in 1 .. 3 do
printfn "%d squared is %d" i (i * i)
let add x y = x + y // int -> int -> int
let add5 = add 5 // partial application
printfn "%d" (add5 10) // 15
let square = fun x -> x * x
[1; 2; 3; 4]
|> List.filter (fun x -> x % 2 = 0)
|> List.map square
|> printfn "%A" // [4; 16]Các đối số cách nhau bằng dấu cách chứ không phải dấu phẩy. Mỗi hàm nhận từng đối số một (currying), nên add 5 trả về một hàm mới. Toán tử pipeline x |> f nghĩa là f x và cho phép viết các phép biến đổi dữ liệu từ trên xuống dưới. Thụt lề có ý nghĩa cú pháp: thân của hàm hoặc vòng lặp phải được thụt vào dưới nó.
Collection, record và option
| Collection | Cú pháp literal | Ghi chú |
|---|---|---|
| List | [1; 2; 3] | Danh sách liên kết bất biến, lựa chọn mặc định |
| Array | [| 1; 2; 3 |] | Kích thước cố định, phần tử khả biến, truy cập chỉ số nhanh |
| Sequence | seq { 1 .. 10 } | Lười, tính khi cần |
| Map | Map ["a", 1; "b", 2] | Từ điển bất biến |
| Set | set [1; 2; 2] | Tập hợp bất biến các giá trị duy nhất |
type Person = { Name: string; Age: int }
let ana = { Name = "Ana"; Age = 30 }
let older = { ana with Age = 31 } // copy with one change
let people = [ ana; { Name = "Ben"; Age = 17 } ]
let adult = people |> List.tryFind (fun p -> p.Age >= 18)
match adult with
| Some p -> printfn "First adult: %s" p.Name
| None -> printfn "No adults"
// First adult: AnaRecord là kiểu bất biến có các trường đặt tên và so sánh bằng theo cấu trúc. F# không có null cho kiểu của chính nó; giá trị có thể thiếu là một Option: Some value hoặc None. Các hàm như List.tryFind trả về option, và trình biên dịch buộc bạn xử lý cả hai trường hợp.
Tính năng đặc trưng: discriminated union và khớp mẫu
Discriminated union mô tả một giá trị chính xác là một trong vài trường hợp có tên, mỗi trường hợp có dữ liệu riêng. match kiểm tra các trường hợp, và trình biên dịch cảnh báo nếu bạn bỏ sót một trường hợp.
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let area shape =
match shape with
| Circle r -> System.Math.PI * r * r
| Rectangle (w, h) -> w * h
[ Circle 1.0; Rectangle (2.0, 3.0) ]
|> List.map area
|> List.iter (printfn "%.2f")
// 3.14
// 6.00Những tính năng nổi tiếng khác là suy luận kiểu (bạn hiếm khi viết chú thích kiểu), đơn vị đo (float<m> không thể cộng với float<s>), và computation expression như task { ... } và async { ... } cho mã bất đồng bộ.
Xử lý lỗi
Với các lỗi có thể đoán trước, F# thành ngữ trả về một Result: Ok value hoặc Error info. Với lỗi bất ngờ, có sẵn ngoại lệ .NET với try ... with, failwith và raise.
let parseAge (s: string) =
match System.Int32.TryParse(s) with
| true, n when n >= 0 -> Ok n
| true, _ -> Error "Age must not be negative"
| false, _ -> Error "Not a number"
match parseAge "abc" with
| Ok n -> printfn "Age %d" n
| Error msg -> printfn "Error: %s" msg
// Error: Not a number
let safeDivide a b =
try Some (a / b)
with :? System.DivideByZeroException -> None
printfn "%A" (safeDivide 10 0) // NoneTryParse trong .NET có tham số out; F# biến nó thành tuple (success, value) mà bạn có thể khớp trực tiếp.
Một chương trình hoàn chỉnh nhỏ: tần suất từ
let text = "the cat and the hat and the bat"
text.Split(' ')
|> Array.countBy id
|> Array.sortByDescending snd
|> Array.truncate 2
|> Array.iter (fun (word, n) -> printfn "%s %d" word n)
// the 3
// and 2Array.countBy id nhóm các từ giống nhau và trả về các cặp (word, count), snd lấy phần tử thứ hai của mỗi cặp để sắp xếp, và kết quả được in bằng một lambda tách tuple.
Học tiếp ở đâu
- Microsoft Learn: tài liệu F#, gồm "Tour of F#" và tài liệu tham chiếu ngôn ngữ.
- Luyện tập trong
dotnet fsi, nơi bạn có thể dán mã và thấy kết quả ngay; kết thúc đầu vào bằng;;. - Học các module
List,Array,Seq,OptionvàResult; phần lớn mã hằng ngày được xây từ chúng. - Xem các framework web và thư viện F# trên NuGet khi đã vững kiến thức cơ bản.
Câu hỏi thường gặp
F# chỉ dành cho Windows thôi sao?
Không. F# chạy trên .NET vốn đa nền tảng, nên bạn có thể build và chạy chương trình F# trên Windows, macOS và Linux.
F# và C# khác nhau thế nào?
Cả hai chạy trên .NET và dùng được cùng các thư viện. C# trước hết là hướng đối tượng và được bổ sung tính năng hàm, còn F# ưu tiên hàm, với dữ liệu bất biến, suy luận kiểu và khớp mẫu là mặc định.
|> nghĩa là gì trong F#?
x |> f là toán tử pipeline và nghĩa là f x. Nó truyền giá trị bên trái làm đối số cuối cùng của hàm bên phải, nên chuỗi biến đổi đọc từ trên xuống dưới.
F# có liên quan đến OCaml không?
Có. F# thuộc họ ML và cú pháp lõi gần với OCaml, nhưng nó được xây cho .NET, mặc định dùng thụt lề có ý nghĩa và có các tính năng riêng như computation expression.