为什么说微软的F#是一门被低估的编程语言?

360影视 日韩动漫 2025-04-03 19:43 2

摘要:在编程领域,数百种编程语言各有千秋,各自适应不同的应用场景。开发者 Bozhidar Batsov 近日发布了一篇主题为《Why F#》的文章,引发了广泛关注,甚至登上了 Hacker News 的热榜。这也让人不禁好奇,F# 这门已经发布了 20 年的编程语

在编程领域,数百种编程语言各有千秋,各自适应不同的应用场景。开发者 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 nameslet 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# 语言指南中找到:

看起来很有前途,对吧?

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 applicationprintfn "Hello, World!"// function definitionlet greet name = printfn "Hello, %s!" namegreet "World"// whitespace is significant, like in Pythonlet foo = let i, j, k = (1, 2, 3) // Body expression: i + 2 * j + 3 * k// conditional expressionslet 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 tupleslet 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 Uniontype Shape = | Circle of radius: float | Rectangle of width: float * height: float// Functing using pattern matchinglet area shape = match shape with | Circle radius -> System.Math.PI * radius * radius | Rectangle (width, height) -> width * heightlet circle = Circle 5.0let rectangle = Rectangle(4.0, 3.0)printfn "Circle area: %f" (area circle)printfn "Rectangle area: %f" (area rectangle)

这里没什么令人震惊的,对吧?

这是另一个稍微复杂一些的例子:

open System// Sample data - simple sales recordstype SalesRecord = { Date: DateTime; Product: string; Amount: decimal; Region: string }// Sample datasetlet 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 pipelinelet 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 resultssalesSummary|> 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# 快速入门教程:

让我印象深刻的一点是,F# 语言设计者非常注重降低入门门槛,通过许多细节优化提升新手体验。以下是几个示例,这些可能对你来说无关紧要,但如果你熟悉 OCaml,就会发现它们的意义所在:

// line comments(* the classic ML comments are around as well *)// mutable valueslet mutable x = 5x // ranges and sliceslet l = [1..2..10]name[5..]// C# method calls look pretty naturallet name = "FOO".ToLower// operators can be overloaded for different typeslet string1 = "Hello, " + "world"let num1 = 1 + 2let num2 = 1.0 + 2.5// universal printingprintfn "%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)这一独特功能。

open Systemopen FSharp.Data// Define the type based on a sample JSON entrytype 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 JSONlet people = PeopleJson.Parse(jsonListString)// Print itprintfn "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.Dataopen Plotly.NETtype LondonBoroughs = HtmlProviderlet 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 JavaScriptdotnet fable# If you want to transpile to TypeScriptdotnet fable --lang typescript# If you want to transpile to Pythondotnet 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 的互操作性影响了一些语言设计(如允许 值)

由微软支持(有些人不喜欢微软),微软对 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_ocamlMelange。从表面上看,Fable 似乎更成熟,但我没有深入研究它们,所以无法给出详细对比。

总体而言,这两者都是强大但小众的语言,未来很难进入主流。不过,由于 .NET 生态庞大,F# 可能更容易在职业环境中找到落脚点,尤其是与 C# 代码库共存时。

总结

问:C# 能做而 F# 不能做的事情是什么?

答:抛出 ReferenceException!

—— 来自 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# 代码能够成功编译,那么它很可能会按照你的预期运行 —— 这在编程界可是个很宝贵的特性!

来源:CSDN一点号

相关推荐