cv.cm

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

7 phút đọc

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 run

Nhó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ểuVí dụGhi chú
int42số nguyên 32 bit
float3.14số thực dấu phẩy động 64 bit (một double của .NET)
decimal19.99msố 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
booltrue, falsegiá 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 needed

F# 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

CollectionCú pháp literalGhi 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
Sequenceseq { 1 .. 10 }Lười, tính khi cần
MapMap ["a", 1; "b", 2]Từ điển bất biến
Setset [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: Ana

Record 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.00

Nhữ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)   // None

TryParse 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 2

Array.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, Option và 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.

Tất cả hướng dẫn lập trình