我编写了一个程序,将文件大小从字节转换为F#中的人类可读格式:
let rec sizeFmt num i =
let suffix="B"
let unit = ["";"Ki";"Mi";"Gi";"Ti";"Pi";"Ei";"Zi"]
match abs num with
| x when x < 1024.0 -> printfn "%3.1f %s%s" num unit.[i] suffix
| _ -> sizeFmt (num / 1024.0) (i+1)
let humanReadable n =
sizeFmt (float n) 0
运行示例:
> humanReadable 33;;
33.0 B
val it : unit = ()
> humanReadable 323379443;;
308.4 MiB
val it : unit = ()
>
问题:
如果我可以将i=0
设置为默认值,那就太好了
sizeFmt
功能。humanReadable
功能。我检查了F#文档,才发现
没有默认参数。所以我必须编写一个包装函数
humanReadable 123;;
。有更好的方法吗?
为了处理像humanReadable 123433.33;;
和float n
这样的int和float类型输入,我必须在包装函数中添加int
。显而易见的问题是:它很容易超过最大a
大小,即2,147,483,647。我想可能有更好的方法,是吗?
答案 0 :(得分:6)
如果sizeFmt
仅由humanReadable
使用,则将其作为内部函数是有意义的。这避免了&#39;参数默认&#39;问题。
此外,标记外部函数inline
会使其接受支持显式转换为n
的任何类型的float
。
let inline humanReadable n =
let rec sizeFmt num i =
let suffix="B"
let unit = ["";"Ki";"Mi";"Gi";"Ti";"Pi";"Ei";"Zi"]
match abs num with
| x when x < 1024.0 -> printfn "%3.1f %s%s" num unit.[i] suffix
| _ -> sizeFmt (num / 1024.0) (i+1)
sizeFmt (float n) 0
humanReadable 123 //works
humanReadable 123433.33 //also works
答案 1 :(得分:4)
一个可能有帮助的F#约定是将主要参数放在参数列表的末尾,然后是辅助参数 - 与OO语言中的约定相反。这使您可以将主要参数传递给函数,例如
let rec sizeFmt i num =
...
123.0 |> sizeFmt 0
它还可以让您轻松创建部分函数,其中包含可选参数:
let humanReadable = sizeFmt 0
在回答2时,没有更好的方法,除非你使sizeFmt
通用并传入1024.0
的类型值,但这可能不会让它变得更简单。< / p>
答案 2 :(得分:2)
在F#中使用可选参数的唯一方法是使用方法而不是函数。要指定参数是可选的,请在其前面加?
。来自文档Android: Google play games services connection error ( java.lang.IllegalStateException: GoogleApiClient must be connected.):
type DuplexType =
| Full
| Half
type Connection(?rate0 : int, ?duplex0 : DuplexType, ?parity0 : bool) =
let duplex = defaultArg duplex0 Full
let parity = defaultArg parity0 false
let mutable rate = match rate0 with
| Some rate1 -> rate1
| None -> match duplex with
| Full -> 9600
| Half -> 4800
do printfn "Baud Rate: %d Duplex: %A Parity: %b" rate duplex parity
let conn1 = Connection(duplex0 = Full)
let conn2 = Connection(duplex0 = Half)
let conn3 = Connection(300, Half, true)
答案 3 :(得分:0)
虽然我知道这不是被问到的,但你知道F#的Units of Measure feature吗?
[<Measure>] type B
[<Measure>] type kB
let bPerKB = 1024.M<B/kB>
let bytesToKiloBytes (bytes : decimal<B>) = bytes / bPerKB
let kiloBytesToBytes (kiloBytes : decimal<kB>) = kiloBytes * bPerKB
这为您提供了一种类型安全的方法来区分字节与千字节,并防止您意外地将千字节值分配给期望字节的函数。
以下是一些示例转换:
> 1024.M<B> |> bytesToKiloBytes;;
val it : decimal<kB> = 1M
> 1145.M<B> |> bytesToKiloBytes;;
val it : decimal<kB> = 1.1181640625M
> 1.M<kB> |> kiloBytesToBytes;;
val it : decimal<B> = 1024M
如果您只需要像上面这样的函数作为一个快速的方法来使大字节值具有人类可读性,那么这肯定是过度的,但如果您需要在许多范围内管理字节值,这可能是合适的。
答案 4 :(得分:0)
现有的答案已经解释了保持包装函数是个好主意,因为这样可以使代码尽可能地模块化。这在一个简单的例子中并不是很明显,但在现实项目中,通过暴露更多参数能够在某个时刻扩展sizeFmt
将是一个很大的优势 - 考虑到你偶尔需要{{1}而不是"Hertz"
(除以1000而不是1024),或者是一种刺激格式模式(五位十进制数字),或一个可本地化的乘数列表,依此类推。
关于第二个问题,转换为"Bytes"
,解决方案非常简单:将float
设为statically-resolved type:
value
这将使let inline humanReadable (value:^T) =
sizeFmt (float value) 0
具有以下类型约束:
humanReadable
用法:
val inline humanReadable :
value: ^T -> unit when ^T : (static member op_Explicit : ^T -> float)
在内部函数中使用humanReadable 42424242.42 // float
humanReadable 4242 // int
humanReadable 42424242424242424242I // Numerics.BigInteger
humanReadable (424242424242424242422424N / 5N) // BigRational
似乎没问题:任何舍入错误都会被一系列的划分消除。