Các tính năng mới trong F# 9
(learn.microsoft.com)- F# 9, đi kèm .NET 9, giảm các vấn đề an toàn phát sinh khi tương tác C#/.NET nhờ kiểu tham chiếu nullable và cải thiện chẩn đoán của trình biên dịch
- Các tính năng như thuộc tính
.Is*cho discriminated union, partial active pattern trả vềbool, computation expression rỗng giúp cú pháp F# hằng ngày ngắn gọn hơn - FSharp.Core bổ sung các hàm ngẫu nhiên cho collection và hỗ trợ collection expression của C#, giúp collection bất biến của F# dễ dùng hơn trong mã .NET khác
- Trình biên dịch phát hiện sớm hơn các vấn đề như dùng attribute sai, phương thức IL vượt quá 65.520, và khả năng hiển thị của private member
- Nhờ tối ưu kiểm tra equality, phạm vi số nguyên, list/array comprehension, một số vòng lặp nhanh hơn 1,25×~8×, và một số array comprehension nhanh hơn tới 10×
F# 9 được cung cấp trong .NET 9
- F# 9 bao gồm các thay đổi nhằm giúp chương trình an toàn hơn, bền bỉ hơn và có hiệu năng tốt hơn
- Có thể dùng trong .NET 9, và SDK .NET mới nhất có thể tải từ trang tải xuống .NET
- Các thay đổi chính được phát triển tại kho mã nguồn mở F#
Thay đổi về tính năng ngôn ngữ
-
Kiểu tham chiếu nullable
- F# được thiết kế để tránh
null, nhưng khi dùng cùng các thư viện .NET viết bằng C#,nullcó thể xuất hiện - F# 9 biểu diễn một cách type-safe kiểu tham chiếu mà null là hợp lệ, như
string | null - Nếu gán
nullvàostringhoặc truy cập trực tiếp.Lengthtrên giá trịstring | null, cảnh báo về khả năng null sẽ xuất hiện - Nếu xử lý trước trường hợp
nulltrong pattern matching, các binding sau đó được coi là giá trị non-null - Trong mã generic, để trả về
nullcần ràng buộc kiểu tham chiếu như'T : not struct - Có thể xem thêm tại Nullable Reference Types in F# 9
- F# được thiết kế để tránh
-
Thuộc tính
.Is*của discriminated union- Discriminated union có thuộc tính
.Is*được tự động sinh cho từng case - Ví dụ, nếu kiểu
Contactcó các caseEmailvàPhone, có thể kiểm tra có phải case cụ thể hay không bằngperson.contact.IsEmail - Trước đây, để kiểm tra tương tự phải viết mã bằng biểu thức
matchnhưEmail _ -> true | _ -> false
- Discriminated union có thuộc tính
-
Partial active pattern trả về
bool- Partial active pattern trước đây phải trả về
Some ()khi khớp thành công vàNonekhi thất bại - Trong F# 9, cũng cho phép trả về
bool - Trong ví dụ khớp chuỗi không phân biệt chữ hoa/thường, có thể trả về trực tiếp kết quả của
String.Equals(..., StringComparison.OrdinalIgnoreCase)
- Partial active pattern trước đây phải trả về
-
Ưu tiên extension method khi có đối số
- Một số thư viện .NET định nghĩa extension method có cùng tên với thuộc tính vốn có của kiểu
- F# 9, để phù hợp với pattern này, sẽ phân giải thành extension method khi có đối số được cung cấp, thay vì báo lỗi type checking
- Trong ví dụ, extension method
X(f: Foo, i: int)cùng tên với thuộc tínhXcủaFoocó thể được gọi ở dạngf.X(1), cho phép thiết lập thuộc tính và chaining lời gọi
-
Computation expression rỗng
- F# 9 hỗ trợ computation expressions rỗng
seq { }tạo một sequence rỗng, và trong mã như HTML DSL có thể biểu diễn block rỗng nhưp { }- Computation expression rỗng dẫn tới lời gọi phương thức
Zerocủa builder - Đây là cú pháp tự nhiên hơn so với
builder { () }trước đây
Cải thiện hash directive và F# Interactive
-
Đối số hash directive không phải chuỗi
- Trước đây, compiler hash directive chỉ cho phép đối số chuỗi được đặt trong dấu ngoặc kép
- Trong F# 9, có thể nhận đối số thuộc kiểu bất kỳ
- Có thể viết
#nowarn 0070thay cho#nowarn "0070", hoặc#time onthay cho#time "on"
-
#helpmở rộng trong F# Interactive- Directive
#helpcủa F# Interactive hiển thị tài liệu của object hoặc function trong REPL - Có thể truyền đối số mà không cần dấu ngoặc kép
- Ví dụ,
#help List.map;;hiển thị mô tả, tham số, giá trị trả về, ví dụ, tên đầy đủ và thông tin assembly - Có thể xem thêm tại bài blog Enhancing #help in F# Interactive
- Directive
-
Cho phép tiền tố
FStrong#nowarn- Trước đây, nếu viết như
#nowarn "FS0057", dù số cảnh báo đúng vẫn phát sinh lỗiInvalid warning number 'FS0057' - Trong F# 9, số cảnh báo vẫn được chấp nhận ngay cả khi có tiền tố
FS #nowarn 57,#nowarn 0057,#nowarn FS0057, cũng như dạng chuỗi"57","0057","FS0057"đều hoạt động- Trong một dự án, nên duy trì cùng một phong cách
- Trước đây, nếu viết như
Cải thiện an toàn và chẩn đoán của trình biên dịch
-
Cảnh báo vị trí
[<TailCall>]không hợp lệ- F# 9 đưa ra cảnh báo nếu attribute
[<TailCall>]được gắn vào vị trí không phù hợp - Ví dụ bao gồm khi gắn vào hàm không đệ quy, giá trị let binding, và giá trị recursive let binding
- Những attribute này không ảnh hưởng đến hành vi mã, nhưng có thể gây nhầm lẫn cho người đọc
- F# 9 đưa ra cảnh báo nếu attribute
-
Tăng cường áp dụng
AttributeTargets- Trình biên dịch thực thi đúng
AttributeTargetstrên let value, function, khai báo union case, constructor ngầm định, struct và class - Có thể ngăn các bug khó thấy, chẳng hạn quên đối số unit trong test Xunit
- Trước đây,
[<Fact>] let ``this test always fails`` = Assert.True(false)không phải là hàm thật nên bị test runner bỏ qua, và khi chạydotnet testvẫn pass - Giờ đây sẽ phát sinh lỗi
error FS0842: This attribute is not valid for use on this language element
- Trình biên dịch thực thi đúng
-
Phục hồi parser
- Nhờ cải thiện phục hồi parser, các tính năng công cụ như tô sáng cú pháp vẫn tiếp tục hoạt động ngay cả với mã chưa hoàn chỉnh về mặt cú pháp trong khi đang chỉnh sửa
- Các đối tượng được phục hồi bao gồm pattern
aschưa hoàn chỉnh, object expression, khai báo enum case, khai báo record, pattern primary constructor phức tạp, unresolved long identifier, nhánh match rỗng, và union case field hoặc field type bị thiếu
-
Độ chính xác của thông báo và vị trí chẩn đoán
- F# 9 bổ sung thông báo chẩn đoán mới và vị trí chẩn đoán chính xác hơn
- Các mục tiêu bao gồm override method mơ hồ trong object expression, abstract member trong non-abstract class, thuộc tính cùng tên với case của discriminated union, số lượng đối số active pattern không khớp, union có field trùng lặp, và việc dùng chung
use!vớiand!trong computation expression - Class có hơn 65.520 phương thức trong IL được sinh ra sẽ gặp lỗi compile-time mới
- Những class như vậy không thể được CLR load, dẫn tới lỗi runtime
-
Tùy chọn khả năng hiển thị thực
- F# có đặc tính ghi private member vào IL dưới dạng internal, nên các dự án không phải F# có thể truy cập F# project thông qua
InternalsVisibleTovà truy cập không phù hợp vào private member - F# 9 cung cấp compiler flag
--realsig+dưới dạng tùy chọn opt-in để sửa hành vi này - Có thể thêm
<RealSig>true</RealSig>vào.fsprojđể sử dụng - Có thể dùng để kiểm tra liệu solution có phụ thuộc vào hành vi cũ hay không
- F# có đặc tính ghi private member vào IL dưới dạng internal, nên các dự án không phải F# có thể truy cập F# project thông qua
Thay đổi trong thư viện chuẩn FSharp.Core
-
Hàm ngẫu nhiên cho collection
- Các module
List,Array,Seqđược bổ sung các hàm lấy mẫu ngẫu nhiên và xáo trộn - Việc dùng F# trở nên dễ hơn trong các kịch bản phổ biến cần tính ngẫu nhiên như khoa học dữ liệu, machine learning và phát triển game
- Mọi hàm đều có ba biến thể
- Biến thể dùng instance
Randomdùng chung, ngầm định và thread-safe - Biến thể nhận instance
Randomlàm đối số - Biến thể nhận hàm
randomizertùy chỉnh phải trả về giá trịfloattừ 0.0 trở lên và nhỏ hơn 1.0
- Biến thể dùng instance
- Các hàm được cung cấp là
Shuffle,Choice,Choices,Sample, mỗi hàm có ba biến thể - Danh sách đầy đủ các hàm và biến thể có tại RFC #1135
- Các module
-
Hành vi theo từng hàm ngẫu nhiên
Shuffletrả về collection mới cùng kiểu và cùng kích thước, trong đó mỗi item được xáo trộn với trọng số đồng đều theo độ dài collection- Với array, cũng có biến thể
InPlacexáo trộn item ngay trong array hiện có Choicetrả về một phần tử ngẫu nhiên đơn lẻ với trọng số đồng đều theo kích thước collectionChoiceschọn N phần tử từ collection đầu vào theo thứ tự ngẫu nhiên, và cùng một phần tử có thể được chọn nhiều lầnSamplechọn N phần tử từ collection đầu vào theo thứ tự ngẫu nhiên, nhưng không chọn cùng một phần tử hai lần- Với
Sample, N không được lớn hơn độ dài collection
-
Constructor không đối số của
CustomOperationAttributeCustomOperationAttributeđược bổ sung constructor không đối số, giúp việc tạo custom operation cho computation expression builder dễ hơn- Trong hầu hết trường hợp, tên tường minh giống với tên method, nên có thể dùng
[<CustomOperation>]thay cho[<CustomOperation("bar")>]
-
Hỗ trợ collection expression của C#
- Từ C#, có thể khởi tạo list và set của F# bằng collection expression
- Ví dụ, có thể viết
FSharpSet<int> mySet = [ 1, 2, 3 ];thay choSetModule.FromArray([1, 2, 3]) - Collection bất biến của F# có thể được dùng khi cần structural equality mà collection trong
System.Collections.Immutablekhông có
Cải thiện hiệu năng
-
Tối ưu kiểm tra equality
- Kiểm tra equality nhanh hơn và giảm cấp phát bộ nhớ
- Trong ví dụ tìm giá trị không tồn tại bằng
Array.containstrên array của kiểu struct, trước đây xảy ra boxing 1.000 lần, nhưng giờ không còn boxing - Trong benchmark hàm array cho struct có 2 member, thời gian trung bình của
ArrayContainsNonexistinggiảm từ 5.190,95ns xuống 766,005ns, và cấp phát giảm từ 24.000B xuống 0 ArrayTryFindNonexistinggiảm từ 5.139,58ns xuống 1.140,515ns, và cấp phát giảm từ 24.024B xuống 24B- Có thể xem thêm tại F# Developer Stories: How we’ve finally fixed a 9-year-old performance issue
-
Chia sẻ field trong struct discriminated union
- Nếu nhiều case của struct discriminated union có field cùng tên và cùng kiểu, chúng có thể chia sẻ cùng vị trí bộ nhớ
- Nhờ đó mức sử dụng bộ nhớ của struct giảm
- Trong ví dụ, kích thước của struct discriminated union chia sẻ các field dựa trên cùng
int64là 16 byte - Phiên bản trước đây phải dùng tên field riêng cho từng case là 60 byte
- Trước đây không cho phép cùng tên field, nên không có vấn đề tương thích nhị phân
-
Tối ưu phạm vi số nguyên
- Trình biên dịch sinh mã tối ưu cho nhiều trường hợp hơn của biểu thức
start..finishvàstart..step..finish - Trước đây, chỉ tối ưu khi kiểu là
int/int32và step là hằng số1hoặc-1 - Các kiểu số nguyên khác và giá trị step khác dùng implementation kém hiệu quả dựa trên
IEnumerable - Giờ đây tất cả các trường hợp này đều được tối ưu
- Trong
for … in start..finish do …,[start..step..finish],[for n in start..finish -> f n], tốc độ tăng từ 1,25× đến 8×
- Trình biên dịch sinh mã tối ưu cho nhiều trường hợp hơn của biểu thức
-
Tối ưu list và array comprehension
- Dạng
for x in xs -> …trong list và array comprehension được tối ưu - Cải thiện đặc biệt rõ ở array
- Tốc độ tăng tối đa 10×, và kích thước cấp phát giảm còn từ một nửa đến một phần tư
- Dạng
Cải thiện công cụ Visual Studio
-
Bật mặc định live buffers
- Tính năng live buffers của Visual Studio trước đây là opt-in, nhưng sau khi được kiểm thử đủ, đã được bật mặc định
- Trình biên dịch nền vận hành IDE sử dụng buffer của file chưa lưu
- Các thay đổi được áp dụng ngay cả khi file chưa được lưu xuống đĩa
- Trước đây, khi rename symbol trong file đã chỉnh sửa nhưng chưa lưu, có thể xảy ra hành vi ngoài dự kiến
-
Code fix xóa dấu ngoặc không cần thiết
- Visual Studio cung cấp code fix để xóa dấu ngoặc không cần thiết
- Ví dụ,
let f (x) = xcó thể đổi thànhlet f x = x, vàlet _ = (2 * 2) + 3thànhlet _ = 2 * 2 + 3 - Đây là tính năng giúp giảm các trường hợp dấu ngoặc gần như chỉ là nhiễu, thay vì phục vụ sự rõ ràng
-
Hỗ trợ custom visualizer cho dự án F#
- Debugger visualizer của Visual Studio cũng hoạt động trong các dự án F#
-
Signature tooltip ở giữa pipeline
- Trước đây, nếu các tham số curried phức tạp đã được áp dụng cho hàm ở giữa pipeline, signature help không được cung cấp
- Giờ đây signature tooltip cho tham số tiếp theo sẽ được hiển thị
1 bình luận
Các ý kiến trên Hacker News
F# đã luôn là ngôn ngữ tôi yêu thích nhất kể từ khi lần đầu tiếp xúc ở đại học
Nó đi trước C# rất xa ở các tính năng như union, null safety, pattern matching, record, suy luận kiểu mạnh hơn và ràng buộc generic
Việc C# dần đưa các tính năng này vào theo thời gian là điều tốt, nhưng đáng tiếc là chúng được đưa vào theo những cách không tương thích với nhau
Do mức đầu tư cho F# nhỏ hơn C# rất nhiều nên có phần tụt lại về tốc độ đổi mới, nhưng đây vẫn là một ngôn ngữ tuyệt vời, nhìn chung tương thích với hệ sinh thái .NET và có thể đạt hiệu năng như C# với ít boilerplate hơn rất nhiều
Có thể xử lý khá dễ bằng cách viết một dự án C# phụ chứa phần “mã keo” cần thiết
Ngoài ra, tôi tò mò liệu có vấn đề cụ thể nào khác mà bạn đang nghĩ tới không
Theo tôi biết, F# 9 cũng hỗ trợ việc dùng tham số generic
ref structmới được thêm vào C# gần đây, và còn có kế hoạch đưa vào các tính năng do chính F# định nghĩaTừ trước tới nay nó đã bắt kịp rất ấn tượng, và xứng đáng được công nhận nhiều hơn nữa
Đoạn “các lớp có hơn 65.520 phương thức trong IL được sinh ra sẽ gặp lỗi compile-time mới. Những lớp như vậy CLR không thể load được nên sẽ gây lỗi runtime” thật khó tưởng tượng nổi
Dù sao thì F# là một ngôn ngữ tuyệt vời
Tôi nghĩ đó là thứ tốt thứ hai Microsoft từng tạo ra sau Excel, và nó biến .NET thành một nền tảng hợp lý
Đây là ngôn ngữ tương đối dễ dạy cho những người đã quen JS hoặc TS, và cũng có năng suất cao
Nó được dùng trong nhiều bối cảnh, từ game engine, backend doanh nghiệp đến ứng dụng desktop
Tôi cho rằng Microsoft đã làm vài sai lầm ở giai đoạn đầu khiến tốc độ phát triển chậm lại, nhưng với vai trò một ngôn ngữ đa dụng thì nó thật sự tốt và cũng tương đối dễ học
Vài năm trước tôi từng thử qua một chút bằng một IDE nhẹ tên là LINQPad, nhưng sau đó không tiếp tục theo dõi ưu nhược điểm hay tình hình phát triển của nó
https://www.linqpad.net/
Ở Phosphor, chúng tôi đã đưa ra một quyết định lớn, đặt cược định hướng công ty và công nghệ lên F# trong vài năm
Sau hơn một năm thử nghiệm, chúng tôi đã viết lại hoàn toàn ứng dụng bằng TypeScript và Rust
Sản phẩm chúng tôi đang xây dựng là công cụ lập trình cho người dùng cuối, nên ranh giới frontend/backend truyền thống trở nên mờ nhạt, và hệ sinh thái .NET không phù hợp lắm với việc này
Ban đầu chúng tôi định dùng Fable để compile mã F# sang JS, Rust, .NET, v.v. nhằm duy trì type safety giữa nhiều công nghệ và thực hiện interop cần thiết
Nhưng trên thực tế, interop giữa nhiều thư viện khó hơn dự kiến rất nhiều, và việc quản lý, cập nhật nhiều dependency cùng binding thật sự rất đau đớn
Tôi vẫn nghĩ giả định rằng F# tạo ra mã đẹp và hiệu quả là đúng, nhưng xét về hệ sinh thái và cách thiết kế, nó chỉ phù hợp tốt với các ứng dụng có ranh giới frontend/backend truyền thống rõ ràng
Trong những trường hợp đó, có lẽ F# sẽ chỉ được dùng ở backend
Hai công nghệ tôi đang kỳ vọng nhất hiện nay là Effect và Moonbit, những thứ chúng tôi đang dùng nội bộ
Thư viện Schema của Effect lấp được nhiều khoảng trống trong hệ thống kiểu của TS, còn Moonbit trông giống một phiên bản F# hiện đại thoát khỏi phụ thuộc MS/.NET
Moonbit được thiết kế bởi tác giả của ReScript, có thể xem là Fable cho OCaml; nó được thiết kế rất tốt và compile trực tiếp ra JS tối ưu hóa, WASM và output native
Effect thì chúng tôi đang dùng trong môi trường production, còn Moonbit thì chưa, nhưng tiềm năng của nó với tư cách một ngôn ngữ được tạo ra cho thế giới ưu tiên AI là khá lớn
Logic nghiệp vụ cốt lõi và các validator được viết bằng F#, phần còn lại của ứng dụng frontend viết bằng TypeScript, backend viết bằng C#
Tức là chỉ đặt logic cốt lõi và phần validation trong F#, còn mọi đầu vào/đầu ra do TS và C# đảm nhiệm
Tôi nhớ đó là một sản phẩm tương tự và được viết bằng F#, nhưng gần đây tôi không theo dõi tình hình
Effect khá tốt, và tôi ước nó cũng có ở các ngôn ngữ khác ngoài TypeScript
MoonBit trông giống một ngôn ngữ lập trình độc quyền riêng, nên tôi hơi do dự khi chuyển sang đó thay vì một ngôn ngữ đã được biết đến rộng rãi; tôi tò mò bạn nhìn nhận điểm này thế nào
Tôi từng có một lớp mật mã học cho phép chọn bất kỳ ngôn ngữ nào dùng .NET, và bài làm bằng F# dễ đọc hơn hẳn so với của những người khác
Tôi muốn dùng nó thường xuyên hơn, nhưng công việc khoa học dữ liệu gần như 100% là Python
F# 9 cũng được hưởng gần như toàn bộ các cải thiện hiệu năng của chính .NET 9
Đặc biệt là các cải thiện về object escape analysis rất lớn
https://devblogs.microsoft.com/dotnet/performance-improvemen...
Tôi thật sự nhớ khoảng thời gian làm việc với F#
Đây là một ngôn ngữ có năng suất cao, nên chỉ việc tiếp tục theo dõi các bản cập nhật cũng đã thấy thú vị
Xét đến quy mô cộng đồng và sự thờ ơ mà Microsoft đôi khi thể hiện, tôi nghĩ hỗ trợ công cụ của nó khá tốt
Bất tiện lớn nhất là độ chính xác của code test coverage
Gần đây tôi có thử F# một chút, và từ góc nhìn của người đến từ Python thì tôi rất thích việc có thể thử nghiệm đủ thứ bằng REPL
Không biết các lập trình viên F# có kinh nghiệm có dùng như vậy không
Mùa đông này tôi muốn làm một dự án backend web nhỏ để tìm hiểu thêm về ngôn ngữ và hệ sinh thái
Tôi nghe nói phía HTTP thì Oxpecker khá tốt, nhưng không biết có khuyến nghị nào cho client hay driver PostgreSQL không
Tôi không thích ORM
https://lanayx.github.io/Oxpecker/
https://monazita.gitlab.io/monazita/ là thứ được tạo ra cho dự án cá nhân trong lúc học F#, về cơ bản hoạt động được nhưng vẫn còn chỗ để trau chuốt thêm, và chỉ dành riêng cho PostgreSQL
https://github.com/jacentino/DbFun được trau chuốt hơn dự án trước và hỗ trợ nhiều cơ sở dữ liệu
Có lẽ nên bắt đầu từ đó
Nếu chưa chắc về API, ngay cả trong codebase thực tế tôi cũng nhấn “Send to F# interactive” để chạy và thử nghiệm bên trong module
Tôi cũng dùng nó để thử thư viện mới, benchmark nhanh, hoặc thay thế script PowerShell
Có thể làm cho script F# chạy được bằng shebang
#!/usr/bin/env -S dotnet fsi, nên tôi thường dùng nó như một lựa chọn thay cho bash/Python trong các script quanh dự án .NET đã cài sẵn dotnet-sdkThường thì nó chạy nhanh hơn và nhìn chung cũng ngắn gọn tương đương
Cảm nhận cá nhân là cú pháp và idiom của F# hợp với kiểu lập trình REPL ghép các đoạn code nhỏ lại hơn C#
C# thường cần nhiều cấu trúc hướng đối tượng hơn
Tôi thắc mắc quản lý phiên bản của F# diễn ra như thế nào
Có nhiều cải thiện chất lượng sống trông khá hay, nhưng xét theo semantic versioning thì không có phá vỡ tương thích nên việc đổi phiên bản chính có vẻ không chính đáng, và nếu dự án không dùng semantic versioning thì đây cũng có vẻ không phải là bước nhảy lớn về tính năng ngôn ngữ đến mức nâng từ 8 lên 9
Ở bình luận khác có nhắc đến .NET 9 mới ra gần đây, nên tôi tự hỏi có phải là để đi cùng số phiên bản .NET không
Tôi không biết họ cố ý chuyển vào thời điểm khớp với phiên bản .NET hay chỉ là trùng hợp
Ví dụ C# đã phát hành C# 13 tương ứng với .NET 9
dotnet buildNó bao gồm msbuild, compiler, các gói NuGet, v.v.
Ví dụ nhóm F# có thể đã đưa ra thay đổi ngôn ngữ giữa 8 và 9, nhưng ngay cả nếu không làm vậy, họ vẫn có thể vì lý do nào đó phát hành thay đổi compiler hoặc msbuild cần .NET 9
Nếu không phải chờ môi trường triển khai cài .NET mới nhất, lập trình viên thường chỉ cần nâng code lên phiên bản runtime mới nhất
Ngày nay nhu cầu đó cũng ít hơn nhờ các thay đổi MSBuild giúp tạo bản triển khai self-contained, nhưng đây vẫn là điều đáng biết
Chỉ là C# đi trước 4 số
Dù sao thì tôi cũng nghĩ semantic versioning bị đánh giá quá cao
Tôi thắc mắc tình trạng của F# như một lựa chọn thay thế C# khi làm ứng dụng GUI trên Windows hiện ra sao
Cũng tò mò không biết có công ty nào dùng F# cho mục đích này không
https://github.com/fsprojects/Avalonia.FuncUI
https://fabulous.dev/ nhắm tới Avalonia/MAUI/Xamarin
https://github.com/kekyo/epoxy hỗ trợ Avalonia và WPF
Nếu bạn quan tâm liệu các công ty có dùng cho mục đích này không, tôi có thể hỏi xung quanh
Tôi chưa từng trực tiếp thử F#, nhưng khi xem qua thì tài liệu này trông rất tuyệt: https://fsharpforfunandprofit.com/
Nó cũng tốt cho lập trình viên C# giàu kinh nghiệm lẫn người tương đối mới học lập trình
Dạo này trang gần như không được cập nhật nên khó tìm thảo luận về tính năng mới của F# 9, nhưng các bài viết hiện có rất xuất sắc để hiểu những khái niệm như
applyvàbind