F#奇妙游(8):计量单位
F#的计量单位
从各个意义上,都可以说计量单位很重要。度量衡的统一是社会化大生产和市场交换的基石。这句正确的废话跟F#没有半毛钱的关系,直到我看帮助看到了这个:
[<Measure>] type m
let l = 1<m>
脑袋顿时秃起来哦不兴奋起来。仔细看了看几个帖子,发现这个东西是F#的一个特性,叫做Units of Measure。
其实网上提的不太多,本来F#的内容就少。
- 比较高访问量的:F# Units of Measure
- Wikibooks上的:F Sharp/Units of Measure
- 一个停更很久的小伙子“要你命3000”:BrendoTRON3000’s Programming Blog有两个帖子。
为什么需要这个计量单位
这个特性看起来很美好,编程序的时候能够把单位写在变量的后面,可以自动检查单位的正确性,比如:
// open FSharp.Data.UnitSystems.SI.UnitSymbols
// 或者自己定义下:
[<Measure>] type m
[<Measure>] type s
let speed = 10<m/s>
let time = 5<s>
let distance = speed * time
这里距离自动推导出距离的单位m
,而且speed * time
的结果也是m
,如果写成speed + time
就会报错,因为单位不匹配。
好多帖子里面还提到NASA把单位搞错,导致火箭坠毁,这个我没仔细去找相关的资料。不过老美那个没文化的,全球都在搞国际单位制,就他抱着英制不放,看NACA(NASA早期的名字)的文章简直是费了劲。
只是这个单位写法和用法,都是编译期的,在运行期间,这个单位就没有了。没有办法访问,没有办法转字符串,没有办法获取和判断。唯一的用处就是在编译时候检查单位的正确性。
这么好的奇淫巧技,微软到底是怎么让它进入F#的呢?我思考了非常久,也没有想明白。因为这个东西这么好的话,C/C++/Java肯定早就要搞起来了啊?为什么呢?
为什么C/C++/Java没有这个计量单位的功能
我想了很久,也没有想明白。我觉得可能是因为这个东西太复杂了,而且用处不大。比如我写了一个函数:
let f (x:float) = x + 1.0
这个函数的输入是float
,输出也是float
,那么我在调用的时候,就不需要写f 1.0f
,因为编译器会自动推导出来。如果我写成:
let f (x:float<m>) = x + 1.0<m>
有两种可能:
- 通过命名约定、检查工具、单元测试,不管是什么东西,在别的主流工程语言中,这个问题不复存在。所以这个功能就没有必要了。
- 这个功能实现起来跟人类实现度量衡统一一样难,最聪明的物理学家,到现在连功的单位都没有统一,简直是人类之耻。
- F#做这个功能实在是太简单?所以语言实现的胸弟随便就做上了?
为了开心和不开心,我还是把跟这个相关的东西都看了一遍,头发少了好几根。
真正有用的Unit of Measure
说这个玩意没有用是一方面,说计量单位没有用那就是胡扯。有一个很好的包叫做CoolProp
,可以计算各种物质的物理性质,比如下面的代码输入是温度和压力,输出是密度。这个密度的单位是kg/m^3
。
let water = CoolProp.PropsSI("D","T",300.0,"P",101325.0,"Water")
这个包C++编的,提供了.NET接口,叫做SharpProp,用F#可以直接调用。
let water = SharpProp.PropsSI("D","T",300.0,"P",101325.0,"Water")
这么下起来是不是很没有牌面?
.NET程序员(准确地说是C#程序员)大神,做了一个很有用的单位系统。这个东西叫做UnitsNet。这个东西是用C#写的,但是F#也可以直接调用。
open System
open SharpProp
open UnitsNet.Units
open UnitsNet.NumberExtensions.NumberToPressure
open UnitsNet.NumberExtensions.NumberToTemperature
open UnitsNet
let fluid (f: FluidsList) (p: float) (T: float) =
(new Fluid(f))
.WithState(Input.Pressure(p.Pascals()), Input.Temperature(T.Kelvins()))
type Fluid with
member this.density = this.Density.KilogramsPerCubicMeter
member this.pressure = this.Pressure.Pascals
member this.temperature = this.Temperature.Kelvins
member this.specificHeat = this.SpecificHeat.JoulesPerKilogramKelvin
member this.viscosity =
match this.DynamicViscosity with
| v when v.HasValue -> v.Value.NewtonSecondsPerMeterSquared
| _ -> Double.NaN
member this.kinematicViscosity =
match this.KinematicViscosity with
| v when v.HasValue -> v.Value.SquareMetersPerSecond
| _ -> Double.NaN
let density (f: FluidsList) (p: float) (T: float) =
(new Fluid(f))
.WithState(Input.Pressure(p.Pascals()), Input.Temperature(T.Kelvins()))
.Density.KilogramsPerCubicMeter
let specificHeat (f: FluidsList) (p: float) (T: float) =
(new Fluid(f))
.WithState(Input.Pressure(p.Pascals()), Input.Temperature(T.Kelvins()))
.SpecificHeat.JoulesPerKilogramKelvin
let viscosity (f: FluidsList) (p: float) (T: float) =
match
(new Fluid(f))
.WithState(Input.Pressure(p.Pascals()), Input.Temperature(T.Kelvins()))
.DynamicViscosity
with
| v when v.HasValue -> v.Value.NewtonSecondsPerMeterSquared
| _ -> Double.NaN
let kinematicViscosity (f: FluidsList) (p: float) (T: float) =
match
(new Fluid(f))
.WithState(Input.Pressure(p.Pascals()), Input.Temperature(T.Kelvins()))
.KinematicViscosity
with
| v when v.HasValue -> v.Value.SquareMetersPerSecond
| _ -> Double.NaN
[<EntryPoint>]
let main argv =
for T in 280..10..350 do
let water = fluid FluidsList.Water 101325.0 (float T)
printfn
$"%20f{water.density} kg/m³%20f{water.pressure} Pa%20f{water.temperature} K%20e{water.viscosity} Pa·s%20e{water.kinematicViscosity} m²/s%20f{water.specificHeat} J/kg·K"
printfn ""
for T in 280..10..500 do
let air = fluid FluidsList.Air 101325.0 (float T)
printfn
$"%20f{air.density} kg/m³%20f{air.pressure} Pa%20f{air.temperature} K%20e{air.viscosity} Pa·s%20e{air.kinematicViscosity} m²/s%20f{air.specificHeat} J/kg·K"
0 // return an integer exit code
很简单的就能把各个单位在运行期的行为都统一起来。这个东西是真正有用的。
UnitsNet的功能举例
UnitsNet把一个带单位的量描述为(数值,单位)的组合,不同的单位之间的转换都在运行时可以获得,各单位的名称也都是在运行时可以获得的。这个东西的功能非常强大,可以用来做各种单位转换,比如下面的代码就是把压力从帕斯卡转换为其他单位。
open UnitsNet
open UnitsNet.Units
open UnitsNet.NumberExtensions.NumberToPressure
// 用不同的单位定义一个量
let p = 101325.0.Pascals()
let p1 = 1.0.Atmospheres()
let p2 = 760.0.Torr()
let p3 = 14.695948775513.PoundsForcePerSquareInch()
let p4 = 1013.25.Millibars()
let p5 = 1013.25.Hectopascals()
// 把一个量转化为不同的单位
printfn $"%f" p.Atmospheres
printfn $"%f" p.Torr
printfn $"%f" p.PoundsForcePerSquareInch
总结
- F#的Unit of Measure是一个很有意思的功能,但是实际上没有什么用。
- UnitsNet实现的单位系统是真正有用的。
- F#调用C#的库还是很好很强大的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~