2015-06-16 69 views
5

我写了一个程序,文件大小从字节转换为F#人类可读的格式: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 =() 
> 

问:

  1. 这将如果我可以将i=0设置为默认值 sizeFmt功能。我检查了F#文档,发现只有 没有默认参数。所以我必须写一个包装函数 humanReadable。有没有更好的办法?

  2. 为了处理像humanReadable 123;;humanReadable 123433.33;;这样的int和float类型输入,我必须在包装函数中添加一个float n。显而易见的问题是:超过最大尺寸2,147,483,647是非常容易的。我想可能有更好的方法,在那里?

+1

你需要在某个时候*强制*深度吗?例如,'42,000,000' - >'“41,025.625 KiB”'?或者它总是被认为是“40.0543 MiB”? – bytebuster

+0

该框架报告文件大小为int64s;这是一种“更好的方式”。 – phoog

+0

@bytebuster它应该是:小数点前不超过三位+最大的适当单位。例如:'40.1 MiB','438.0 KiB','249.8 GiB'。 (我用'%3.1f'格式化它。) – Nick

回答

4

一个F#约定可能会帮助将主要参数放在参数列表和辅助参数的末尾 - 这与OO语言中约定的相反。这使您可以将主要参数传递给您的函数,例如

let rec sizeFmt i num = 
    ... 

123.0 |> sizeFmt 0 

它也可以让您轻松地创建带有可选参数的部分功能填补了:

let humanReadable = sizeFmt 0 

在回答2,无有没有更好的办法,除非你sizeFmt通用,并通过在类型化值为1024.0,但这可能不会让它变得更简单。

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 

如果你只需要像上面的功能,作为一个快速的方法,使一个大的字节值人类可读的,这肯定是矫枉过正,但如果你需要管理字节值在许多尺度上,这可能是适当的。

1

在F#中拥有可选参数的唯一方法是使用方法而不是函数。要指定参数是可选的,请在它之前放置一个?。从文档here

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) 
5

如果sizeFmt仅用于humanReadable,这是有道理的,使其内部函数。这避免了“参数默认”问题。

另外,标记外部函数inline会导致它接受支持显式转换为float的任何类型的n

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 
0

现有的答案已经解释说,保持包装函数是一个好主意,因为这让代码尽可能多模块化越好。在一个简单的例子中这不会很明显,但在现实生活中,通过暴露更多参数来延长sizeFmt会是一个很大的优势 - 考虑到您偶尔需要"Hertz"而不是"Bytes"(和除法由1000而不是1024)或sting格式模式(五位十进制数字),或者可以本地化的乘法器列表,等等。


至于第二个问题,转化为float,解决的方法很简单:value一个statically-resolved type

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 

使用float内部函数似乎很好:任何舍入错误将被一系列的分割消除。