Is there a default setting in F #?

I wrote a program to convert the file size from bytes to readable in F # format:

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 

Execution Example:

 > humanReadable 33;; 33.0 B val it : unit = () > humanReadable 323379443;; 308.4 MiB val it : unit = () > 

Question:

  • It would be nice if I could set i=0 as the default in sizeFmt funciton. I checked the F # documentation, only found that there is no default parameter. So I have to write a humanReadable wrapper humanReadable . Is there a better way?

  • To process both input data of type int and type float, such as humanReadable 123;; and humanReadable 123433.33;; I have to add float n to the wrapper function. The obvious problem is that it is very easy to exceed the maximum int size, which is 2,147,483,647. I think there may be a better way, is there?

+5
source share
5 answers

If sizeFmt used only by humanReadable , it makes sense to make it an internal function. This avoids the problem with the default setting.

In addition, tagging the external inline function forces it to accept any type n , which supports explicit conversion to 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 
+5
source

One F # convention that can help is to transfer the primary parameters to the end of the list of parameters and secondary parameters first, the opposite of the OO convention. This allows you to pass the main argument to your function, for example.

 let rec sizeFmt i num = ... 123.0 |> sizeFmt 0 

It also makes it easy to create partial functions with added parameters:

 let humanReadable = sizeFmt 0 

In response to 2, there is no better way unless you make sizeFmt generic and pass in a typed value of 1024.0 , but that probably won't simplify it.

+4
source

The only way to have an optional parameter in F # is to use a method instead of a function. To indicate that a parameter is optional, precede it ? . From the documentation 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) 
+1
source

Although I know that it’s not what you are asked about, do you know about the F # Unit function ?

 [<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 

This gives you a safe way to recognize bytes from kilobytes and prevents you from accidentally assigning a kilobyte value to a function that expects bytes.

Here are some conversion examples:

 > 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 

If you just need the function described above as a quick way to make large byte value understandable to humans, this is certainly too great, but if you need to manage byte values ​​on many scales, this may be appropriate.

0
source

Existing answers already explain that saving a wrapper function is a good idea, as it allows you to make the code as modular as possible. This would not be very obvious with a simple example, but in real projects it would be a big advantage to expand sizeFmt at some point by setting more parameters - consider that sometimes you may need "Hertz" instead of "Bytes" (and division by 1000 instead of 1024), or a sting pattern template (five decimal digits), or a localized list of multipliers, etc.


Regarding the second question translating to float , the solution is very simple: make value a statically resolved type :

 let inline humanReadable (value:^T) = sizeFmt (float value) 0 

This will cause humanReadable to have the following type constraint:

 val inline humanReadable : value: ^T -> unit when ^T : (static member op_Explicit : ^T -> float) 

Using:

 humanReadable 42424242.42 // float humanReadable 4242 // int humanReadable 42424242424242424242I // Numerics.BigInteger humanReadable (424242424242424242422424N / 5N) // BigRational 

Using float in an internal function seems to be in order: any rounding errors will be eliminated by a series of divisions.

0
source

All Articles