在编程领域,数百种编程语言各有千秋,各自适应不同的应用场景。开发者 Bozhidar Batsov 近日发布了一篇主题为《Why F#》的文章,引发了广泛关注,甚至登上了 Hacker News 的热榜。这也让人不禁好奇,F# 这门已经发布了 20 年的编程语言,如今有哪些值得关注的特别之处?
对此,一位网友评论道:“F# 与 AI 非常契合,所有 AI 模型都能轻松生成惯用的 F# 代码。更重要的是,F# 拥有一个非常强大的类型系统,这使得在实现之前,通过 AI 模型准确建模问题,能够有效捕获潜在的幻觉错误。”本文中,Batsov 深入探讨了 F# 的优缺点、生态系统及开发工具,揭示了这门语言在现代编程和AI领域中的独特优势。
原文链接:https://batsov.com/articles/2025/03/30/why-fsharp/
作者 | Bozhidar Batsov
责编 | 苏宓
出品 | CSDN(ID:CSDNnews)
如果几个月前有人告诉我,我会在时隔 15 年之后再次使用 .NET,我肯定会对此嗤之以鼻。在我早期职业生涯中,我曾接触过 .NET 和 Java,尽管 .NET 在某些方面比 Java 做得更好(毕竟它有机会从 Java 早期的错误中吸取教训),但我最终选择了 Java,因为它是真正的跨平台环境。
其实在过去几年里,我断断续续地也在玩 OCaml(一个函数式、指令式、模块化、面向对象的通用的编程语言),可以说它已经成为我最喜欢的编程语言之一,就像 Ruby 和 Clojure 一样。我对 OCaml 的研究让我最近开始关注 F#——一种面向 .NET 的 ML 语言,由微软开发。它是 C#(主要是面向对象的语言)的函数式对应版本,也是最新的 ML 语言之一。

什么是 F#?
不幸的是,没有人能被告知“Matrix”是什么,你必须自己去看。
——莫菲斯,《黑客帝国》
在讨论 F# 之前,我们首先应该回答“F# 是什么?”这个问题。我借用官方页面的一些描述来回答:
F# 是一种通用编程语言,用于编写简洁、健壮且高性能的代码。
F# 让你能够编写简洁、自解释的代码,让你的注意力集中在问题领域,而不是编程的细节。
它在保证速度和兼容性的同时做到这一点——它是开源的、跨平台的,并且具有良好的互操作性。
open System // Gets access to functionality in System namespace.
// Defines a list of names
let names = [ "Peter"; "Julia"; "Xi" ]
// Defines a function that takes a name and produces a greeting.
let getGreeting name = $"Hello, {name}"
// Prints a greeting for each name!
names
|> List.map getGreeting
|> List.iter (fun greeting -> printfn $"{greeting}! Enjoy your F#")
趣闻:F# 是让管道操作符 |> 流行起来的编程语言。
F# 拥有众多特性,包括:
轻量级语法
默认不可变
类型推导和自动泛化
一等函数(First-class functions)
强大的数据类型
模式匹配
异步编程
完整的特性集可以在 F# 语言指南中找到:https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/
看起来很有前途,对吧?
F# 1.0 由微软研究院于 2005 年 5 月正式发布。最初,它由微软剑桥研究院的 Don Syme 开发,起源于一个名为 “Caml.NET” 的研究项目,旨在将 OCaml 引入 .NET 平台。2010 年,随着 F# 2.0 的发布,F# 从微软研究院转移到微软的开发者工具部门,成为正式支持的开发语言。
自最初发布以来,F# 一直在不断演进,最近的版本F# 9.0已于 2024 年 11 月发布。恰逢 F# 诞生 20 周年,这时候关注它似乎再合适不过了!
我想尝试 F# 的原因有几个:
.NET 在几年前变成了开源且可移植的,我想看看它的进展如何。
我很好奇 F# 是否相比 OCaml 有额外的优势。
我听说 F# 的开发工具(如 Rider 和 Ionide)体验不错。
我喜欢尝试新的编程语言。
接下来是我对几个方面的初步印象。

语言特性
作为 ML 语言家族的一员,F# 的语法对熟悉 OCaml 的人来说不会有太大惊喜。不过,由于熟悉 OCaml 的人并不多,我要补充一点:Haskell 程序员会很容易上手,Lisp 开发者也会感到亲切。
对于其他人来说,掌握基础知识并不难。
// function application
printfn "Hello, World!"
// function definition
let greet name =
printfn "Hello, %s!" name
greet "World"
// whitespace is significant, like in Python
let foo =
let i, j, k = (1, 2, 3)
// Body expression:
i + 2 * j + 3 * k
// conditional expressions
let test x y =
if x = y then "equals"
elif x
else "is greater than"
printfn "%d %s %d." 10 (test 10 20) 20
// Looping over a list.
let list1 = [ 1; 5; 100; 450; 788 ]
for i in list1 do
printfn "%d" i
// Looping over a sequence of tuples
let seq1 = seq { for i in 1 .. 10 -> (i, i*i) }
for (a, asqr) in seq1 do
printfn "%d squared is %d" a asqr
// A simple for...to loop.
let function1 () =
for i = 1 to 10 do
printf "%d " i
printfn ""
// A for...to loop that counts in reverse.
let function2 () =
for i = 10 downto 1 do
printf "%d " i
printfn ""
// Records
// Labels are separated by semicolons when defined on the same line.
type Point = { X: float; Y: float; Z: float }
// You can define labels on their own line with or without a semicolon.
type Customer =
{ First: string
Last: string
SSN: uint32
AccountNumber: uint32 }
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
// Discriminated Union
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
// Functing using pattern matching
let area shape =
match shape with
| Circle radius -> System.Math.PI * radius * radius
| Rectangle (width, height) -> width * height
let circle = Circle 5.0
let rectangle = Rectangle(4.0, 3.0)
printfn "Circle area: %f" (area circle)
printfn "Rectangle area: %f" (area rectangle)
这里没什么令人震惊的,对吧?
这是另一个稍微复杂一些的例子:
open System
// Sample data - simple sales records
type SalesRecord = { Date: DateTime; Product: string; Amount: decimal; Region: string }
// Sample dataset
let sales = [
{ Date = DateTime(2023, 1, 15); Product = "Laptop"; Amount = 1200m; Region = "North" }
{ Date = DateTime(2023, 2, 3); Product = "Phone"; Amount = 800m; Region = "South" }
{ Date = DateTime(2023, 1, 20); Product = "Tablet"; Amount = 400m; Region = "North" }
{ Date = DateTime(2023, 2, 18); Product = "Laptop"; Amount = 1250m; Region = "East" }
{ Date = DateTime(2023, 1, 5); Product = "Phone"; Amount = 750m; Region = "West" }
{ Date = DateTime(2023, 2, 12); Product = "Tablet"; Amount = 450m; Region = "North" }
{ Date = DateTime(2023, 1, 28); Product = "Laptop"; Amount = 1150m; Region = "South" }
]
// Quick analysis pipeline
let salesSummary =
sales
|> List.groupBy (fun s -> s.Product) // Group by product
|> List.map (fun (product, items) -> // Transform each group
let totalSales = items |> List.sumBy (fun s -> s.Amount)
let avgSale = totalSales / decimal (List.length items)
let topRegion =
items
|> List.groupBy (fun s -> s.Region) // Nested grouping
|> List.maxBy (fun (_, regionItems) ->
regionItems |> List.sumBy (fun s -> s.Amount))
|> fst
(product, totalSales, avgSale, topRegion))
|> List.sortByDescending (fun (_, total, _, _) -> total) // Sort by total sales
// Display results
salesSummary
|> List.iter (fun (product, total, avg, region) ->
printfn "%s: $%M total, $%M avg, top region: %s"
product total avg region)
你可以尝试将上面的代码片段保存到一个名为 Sales.fsx 的文件中,然后这样运行它:
dotnet fsi Sales.fsx
现在你已经知道,F# 是进行临时脚本(ad-hoc scripts)的绝佳选择!此外,直接运行 dotnet fsi 会启动 F# REPL(交互式环境),让你可以随意探索这门语言。
我不会在这里详细介绍 F#,如果你想快速了解 F# 的语法,我建议阅读 F# 快速入门教程:https://learn.microsoft.com/en-us/dotnet/fsharp/tour
让我印象深刻的一点是,F# 语言设计者非常注重降低入门门槛,通过许多细节优化提升新手体验。以下是几个示例,这些可能对你来说无关紧要,但如果你熟悉 OCaml,就会发现它们的意义所在:
// line comments
(* the classic ML comments are around as well *)
// mutable values
let mutable x = 5
x
// ranges and slices
let l = [1..2..10]
name[5..]
// C# method calls look pretty natural
let name = "FOO".ToLower()
// operators can be overloaded for different types
let string1 = "Hello, " + "world"
let num1 = 1 + 2
let num2 = 1.0 + 2.5
// universal printing
printfn "%A" [1..2..100]
我猜其中一些改动可能会引发争议,具体取决于你是否是 ML 语言的原教旨主义者。但在我看来,任何让 ML 语言更受欢迎的改进,都是一件好事。
顺便提一句,F# 处理 Unicode 字符串和正则表达式也非常方便!
经常有人说,F# 主要是 C# 未来功能的试验田。也许这是真的,但由于我没有长期关注这两门语言,所以没法给出自己的判断。不过,我确实惊讶地发现,广受欢迎的 async/await 语法最早竟然是起源于 F# 2.0!
在 2012 年,随着 C# 5 的发布,async/await 语法才正式进入 C#,并逐渐流行开来。这项功能让开发者能够写出像同步代码一样易读,但同时又能享受手写异步代码的所有优势(例如避免 UI 线程被阻塞)。如今,这种模式已经被许多现代编程语言借鉴,比如 Python、JavaScript、Swift、Rust,甚至 C++。
F# 的异步编程方式虽然和 async/await 有些不同,但目标是一样的。实际上,async/await 只是 F# 更早推出的异步机制的简化版本,该机制最早出现在 F# 2.0。
—— Isaac Abraham,《F# in Action》
未来 F# 会如何发展,还需要时间检验。但我认为,C# 很难完全取代 F#。
我还发现了一条来自 2022 年的好消息,表明微软可能会加大对 F# 的投资:
“终于有好消息了!过去 10 年,F# 在微软内部只有 2.5 个人在维护(再加上一些社区贡献者)。但在 2022 年夏天,微软终于决定正式投资 F#,并在布拉格组建了一支完整的 F# 开发团队。我也是这个团队的成员,作为一名多年的 F# 粉丝,我很高兴终于有了真正的进展。”
从 F# 8.0 到 F# 9.0 的变化来看,这支新团队确实做出了不少卓越的工作!

F# 生态系统
短短几天的体验,很难全面评估 F# 的生态,但整体来看,原生的 F# 库和框架并不多。大多数开发者主要依赖 .NET 核心 API 和针对 C# 的第三方库。这在托管语言(如 Scala、Clojure、Groovy)中是很常见的情况,因此也不算意外。
如果你想了解 F# 生态,可以查看 Awesome F#(https://github.com/fsprojects/awesome-fsharp),它整理了 F# 相关的流行库、工具和框架。以下是一些值得关注的项目:
Web 开发相关
Giraffe:基于 ASP.NET Core 的轻量级 Web 开发库,提供函数式开发方式。
Suave:一个简洁的 Web 服务器库,提供组合式的路由和任务管理(Giraffe 受 Suave 启发)。
Saturn:基于 Giraffe 和 ASP.NET Core,提供类似 Ruby on Rails 和 Elixir Phoenix 的 MVC 框架。
Bolero:基于 WebAssembly 和 Blazor 的 F# 前端开发框架。
Fable:将 F# 代码编译为 JavaScript,可与 React、Node.js 等生态集成。
Elmish:MVU(Model-View-Update)架构,适用于 F# 前端开发,常与 Fable 结合使用。
SAFE Stack:端到端的 F# Web 开发栈,结合 Saturn、Azure、Fable 和 Elmish,提供类型安全的开发体验。
数据科学相关
Deedle:类似 Python pandas 的数据分析库。
DiffSharp:支持自动微分和机器学习的库。
FsLab:数据科学工具集,包括可视化和统计分析工具。
目前我还没有深入体验这些库,所以具体的反馈和推荐留到以后再说。

文档
官方文档质量不错,不过有点分散,一部分在微软官网(https://learn.microsoft.com/en-us/dotnet/fsharp/what-is-fsharp),另一部分在 F# 软件基金会的 fsharp.org 站点(https://fsharp.org/)上,这点让我有些疑惑。
我特别喜欢以下几部分文档:
F# 风格指南:https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/
F# 设计 RFC 仓库(所有语言都应该有类似的 RFC 机制!):https://github.com/fsharp/fslang-design
F# 标准库 API:https://fsharp.github.io/fsharp-core-docs/
此外,F# for Fun and Profit(https://fsharpforfunandprofit.com/)也是一个不错的学习资源(尽管内容有点旧)。

开发工具
F# 的开发工具支持一直是个问题。过去,F# 主要在 Visual Studio 上体验良好,但在其他编辑器上的支持较差。不过,过去十年情况有了很大改善。
2014 年,Tomas Petricek、Ryan Riley 和 Dave Thomas 开发了 FSharp.Compiler.Service(FCS)包,将 F# 编译器、编辑器工具和脚本引擎整合成一个库。这使得 F# 能够适配更多编辑器,例如:
JetBrains Rider —— .NET 生态的强大 IDE,对 F# 友好。
Emacs(fsharp-mode)
Zed(第三方插件)
Helix(内置 F# 支持)
VS Code(Ionide 插件)
不过,我还是要提一下,我发现 F# 的工具链在某些方面有所欠缺:
fsharp-mode 目前还不支持 TreeSitter(至少暂时如此),而且似乎开发并不活跃。从代码来看,它是从 caml-mode 派生出来的。
Zed 对 F# 的支持非常简陋。
在 VS Code 中,选择区域的扩展和收缩功能竟然是坏的,这对于 F# 这样一个本应在 VS Code 上有良好支持的语言来说,实在让人费解。
我确实很难适应 VS Code 的快捷键绑定(对我来说,修饰键和功能键太多了),以及它的编辑模式。所以,我可能会继续坚持使用 Emacs,或者抽出时间好好研究 neovim!
好消息是,大家似乎都在使用同一个代码格式化工具 Fantomas,包括 F# 官方团队,这一点很棒!不过,F# 的 linter(代码检查工具)情况就不太理想,目前唯一流行的 FSharpLint 似乎已经变成弃坑项目了。不过话说回来,F# 的编译器本身已经足够强大,所以对 linter 的需求也没有那么高。
看起来微软并没有特别投入到 F# 工具链的支持中,几乎所有的核心工具项目都是由社区驱动的。
至于 AI 辅助编程工具(如 GitHub Copilot),对 F# 的支持也不错,不过我暂时没有深入体验。
最终来说,任何支持 LSP(语言服务器协议)的编辑器都应该可以胜任 F# 开发。
顺便说一下,我在使用 F#(以及 OCaml) 编程时有一个有趣的发现:当你使用一门拥有优秀类型系统的语言时,对编辑器的需求其实并不高。大多数时候,我只需要一些内联类型信息(比如类似 CodeLens 的功能)、自动补全,以及能够方便地将代码发送到 F# 交互式环境(fsi 就够用了。简单才是终极的复杂……
其他值得关注的工具:
Paket - 一个用于 .NET 项目的依赖管理工具。可以把它看作是 bundler(Ruby)、npm(Node.js)或 pip(Python)在 .NET 生态中的等价物。
FAKE - 一个基于 F# 的 DSL,用于构建任务等。它类似于 Ruby 的 rake,一些人甚至认为这是在现有 .NET 项目中“偷偷”引入 F# 的最佳方式。

应用场景
.NET 生态本身非常庞大,所以 F# 理论上适用于任何 .NET 相关领域。
不过,我觉得 F# 特别适合数据分析,因为它提供了类型提供程序(Type Providers) 这一独特功能。
下面是一个使用 JSON 类型提供程序的示例:
#r "nuget: FSharp.Data"
open System
open FSharp.Data
// Define the type based on a sample JSON entry
type PeopleJson = JsonProvider
[
{ "name": "Alice", "age": 30, "skills": ["F#", "C#", "Haskell"] }
]
""">
// Simulated JSON list (could be loaded from file or API)
let jsonListString = """
[
{ "name": "Alice", "age": 30, "skills": ["F#", "C#", "Haskell"] },
{ "name": "Bob", "age": 25, "skills": ["F#", "Rust"] },
{ "name": "Carol", "age": 28, "skills": ["OCaml", "Elixir"] },
{ "name": "Dave", "age": 35, "skills": ["Scala", "F#"] },
{ "name": "Eve", "age": 32, "skills": ["Python", "F#", "ML"] },
{ "name": "Frank", "age": 29, "skills": ["Clojure", "F#"] },
{ "name": "Grace", "age": 27, "skills": ["TypeScript", "Elm"] },
{ "name": "Heidi", "age": 33, "skills": ["Haskell", "PureScript"] },
{ "name": "Ivan", "age": 31, "skills": ["Racket", "F#"] },
{ "name": "Judy", "age": 26, "skills": ["ReasonML", "F#"] }
]
"""
// Parse the JSON
let people = PeopleJson.Parse(jsonListString)
// Print it
printfn "People in the list:\n"
for p in people do
printfn "%s (age %d) knows:" p.Name p.Age
p.Skills |> Array.iter (printfn " - %s")
printfn ""
我第一次看到这个时,感觉就像魔法一样。F# 能够从一个小的数据样本中推断出数据的结构和类型,并自动生成解析器。你可以将代码保存为 TypeProvidersDemo.fsx 文件,然后像这样运行它:
dotnet fsi TypeProvidersDemo.fsx
但这还不止于此,你还可以轻松地从 HTML 表格中提取数据并对其进行可视化:
#r "nuget:FSharp.Data"
#r "nuget: Plotly.NET, 3.0.1"
open FSharp.Data
open Plotly.NET
type LondonBoroughs = HtmlProvider
let boroughs = LondonBoroughs.GetSample().Tables.``List of boroughs and local authorities``
let population =
boroughs.Rows
|> Array.map (fun row ->
row.Borough,
row.``Population (2022 est)``)
|> Array.sortBy snd
|> Chart.Column
|> Chart.show
如果你运行该脚本,你将在浏览器中看到一张展示伦敦各区人口的精美图表。不错吧!

在这里,我们也体会到了 F# 脚本中使用外部库(例如 Plotly.NET)的便捷性!
展望未来,我认为 F# 适用于后端服务,甚至可以用于构建全栈应用,尽管我尚未深入尝试 F# 在这一领域的原生解决方案。
Fable 和 Elmish 使 F# 成为客户端编程的可行选择,并可能为你在日常工作中引入 F# 提供一种简单的途径。
注意: 传统上,Fable 主要用于转换为 JavaScript,但自 Fable 4 以来,它还支持转换为 TypeScript、Rust、Python 等其他语言。
以下是将 F# 代码转换为其他语言的简单示例:
# If you want to transpile to JavaScript
dotnet fable
# If you want to transpile to TypeScript
dotnet fable --lang typescript
# If you want to transpile to Python
dotnet fable --lang python

社区情况
我对 F# 社区的初步印象是规模相对较小,可能甚至比 OCaml 社区还要小。F# 相关讨论最活跃的地方似乎是 Reddit 和 Discord(Reddit 上列出的那个)。
目前,我还不太清楚微软在 F# 社区中扮演什么角色,因为整体上很少看到他们的参与。
对我来说,社区规模小并不是问题,只要它足够活跃、充满生机就行。此外,我也发现自己往往更容易融入小型社区。当年从 Java 转向 Ruby 时,这种社区氛围和归属感的变化简直是天壤之别。
关于 F# 的书籍、社区网站或博客并不多,但这也是我预料之中的情况。
我发现的一些比较重要的社区项目包括:
Amplifying F# —— 一个致力于推广 F# 并吸引更多企业参与的项目
F# for Fun and Profit —— 一个收集了 F# 教程和文章的网站
F# Lab —— 一个社区驱动的 F# 数据科学工具包
F# Weekly —— 每周更新的 F# 相关新闻和动态
总的来说,F# 还有很多推广和吸引新用户的空间,但对于一个已经存在 20 年的项目来说,这并不容易。我仍然不太理解为什么微软没有更积极地推广 F#,因为我觉得它本可以成为微软的一个很好的营销工具。
整体而言,我目前还不太有资格对 F# 社区做太多评价。

受欢迎程度
是否在乎一门编程语言的“流行度”,这取决于个人。有些人经常问我,为什么花这么多时间在一些几乎不会带来职业机会的语言上,比如:
Emacs Lisp
Clojure
OCaml
F#
当然,职业发展很重要,但对我来说,还有其他因素:
编程的乐趣(F# 里的 “F” 代表 “Fun”)
学习新的编程范式和思想
挑战自己,换一种思维方式去编程
从大多数主流指标来看,F# 并不是一门流行的语言。它在 TIOBE、Stack Overflow 或大多数招聘网站上的排名都不高。但如果与其他主流的函数式编程语言相比,F# 也没有更不受欢迎。遗憾的是,函数式编程至今仍未进入主流,或许永远不会。

F# 与 OCaml 的比较
F# 最初的目标是将 OCaml 的优势带入 .NET,同时让 .NET 生态可以使用 OCaml 风格的强类型函数式编程。最初的任务相对明确:重新实现 OCaml 语言的核心部分,并移植部分标准库,使其可以在 .NET 运行时(CLR)上运行。
如果你问大多数人 F# 优于 OCaml 的优缺点,你可能会得到以下的答案:
F# 的优点:
运行在 .NET 平台上:可以使用大量 .NET 生态中的库
由微软支持
对 OO 开发者来说更容易上手:语法比 OCaml 略微简单、编译器的错误和警告更易理解、调试更友好
强大的异步编程支持
具有 OCaml 所没有的一些独特特性,比如匿名记录、活动模式、计算表达式、序列推导、类型提供器、单位类型
F# 的缺点:
运行在 .NET 上(这既是优点也是缺点):与 .NET 的互操作性影响了一些语言设计(如允许 null 值)
由微软支持(有些人不喜欢微软),微软对 F# 的资源投入似乎较少,长期支持不明朗
命名风格:F# 偏向 PascalCase 和 camelCase,而 OCaml 使用 snake_case
缺少一些 OCaml 的特性,如一流的模块和函子(First-class Modules & Functors)、广义代数数据类型(GADTs)
没有 OCaml 那只可爱的骆驼 Logo
F# 这个名字虽然很酷,但在搜索和文件命名时可能会带来困扰(经常会看到 FSharp)
F# 和 OCaml 都可以编译为 JavaScript,F# 通过 Fable,OCaml 通过 Js_of_ocaml 和 Melange。从表面上看,Fable 似乎更成熟,但我没有深入研究它们,所以无法给出详细对比。
总体而言,这两者都是强大但小众的语言,未来很难进入主流。不过,由于 .NET 生态庞大,F# 可能更容易在职业环境中找到落脚点,尤其是与 C# 代码库共存时。

总结
问:C# 能做而 F# 不能做的事情是什么?
答:抛出 NullReferenceException!
—— 来自 F# 社区的笑话
总体来说,我比预期更喜欢 F#!它让我想起当年学习 Clojure 的经历 —— 当时 Clojure 是最实用的 Lisp 语言之一,主要得益于它与 Java 生态的良好互操作性。
如果 .NET 从一开始就是跨平台和开源的,也许 ClojureCLR 会和 Clojure 一样流行,而 F# 也可能会拥有更大的社区和更广泛的应用。事实上,如果没有 .NET Core,我可能根本不会再碰 .NET,而我相信有很多人和我一样。F# 直到 2010 年才开源,这无疑影响了它的早期推广。
即便如此,F# 依然是一门值得学习的语言,尤其适合那些熟悉 .NET 的开发者。对于想要深入了解 ML(元语言)家族语言的人来说,F# 也是一个不错的选择。
更重要的是,Fable 让 F# 可以运行在 JavaScript、Dart、Rust 和 Python 等多个环境中,这进一步拓宽了它的适用场景。
所以,为什么要学 F#?
F# 社区有句话——F# 里的 “F” 代表 “Fun”(有趣)。
在我短暂的体验中,这一点确实得到了验证!
此外,如果你的 F# 代码能够成功编译,那么它很可能会按照你的预期运行 —— 这在编程界可是个很宝贵的特性!


财经自媒体联盟

4000520066 欢迎批评指正
All Rights Reserved 新浪公司 版权所有