Reputation: 3647
I'm trying to get the exact equivalent (not functional) of this vb.net code in F#:
Function FastPow(ByVal num As Double, ByVal exp As Integer) As Double
Dim res As Double = 1
If exp < 1 Then
If exp = 0 Then Return res
exp = -exp
num = 1 / num
End If
Do While exp > 1
If exp Mod 2 = 1 Then
res = res * num
num = num * num
exp = exp >> 1
Loop
Return res * num
End Function
I wrote this:
let FastPow num exp =
let mutable ex = exp
let mutable res = 1
let mutable n = num
if ex < 1 then
if ex = 0 then res
ex <- -ex
n <- 1 / n
while ex > 1 do
if (ex % 2 = 1) then
res <- res * n
n <- n * n
exp >>> 1
res * n
but in the line "if ex = 0 then res" at res I got an error:
"This expression has type int but is here used with type unit".
I cannot understand why it gives me that error.
Edit: i actually got a warning as well:
"This expression should have type 'unit', but has type 'int'."
at "if (ex % 2 = 1) then"
Upvotes: 3
Views: 6676
Reputation: 3647
Thanks for the answers. This is the current non-functional version.
let FastPow num exp =
let mutable ex = exp
let mutable res = 1.0
let mutable n = num
if ex = 0 then 1.0
else
if ex < 1 then
ex <- -ex
n <- 1.0 / n
while ex > 1 do
if (ex % 2 = 1) then res <- res * n
n <- n * n
ex <- ex >>> 1
res * n
Now that I have a working version I will try to make it more functional but that's outside the scope of this question. EDIT: I got better results that I expected so I will post the recursive version optimized for speed (slightly faster than the iterative version and about 10% faster than the C# iterative version (!!!) in my computer):
let rec loop res num exp =
if exp = 0 then res
elif (exp % 2) = 1 then loop (res * num) (num * num) (exp / 2)
else loop res (num * num) (exp / 2)
let FP num exp =
let n = if exp < 0 then 1.0 / num else num
loop 1.0 n (Math.Abs(exp))
Upvotes: 0
Reputation: 81536
In F#, a function's return value is the last expression evaluated in the function. So, lets focus on the following:
if ex < 1 then
if ex = 0 then res (* <--- this is not an early return *)
ex <- -ex (* <--- F# evaluates this code after the *)
n <- 1 / n (* if statement *)
Additionally, if statements have return values, which also happens to be the last value executed in the if statement. If an if statement isn't the return value of a function, it should have the return type unit
. Notice that variable assignment has a return type of unit
.
We need to rewrite your code to accomodate your early return, so we can do this:
let FastPow2 num exp =
if exp = 0 then 1
else
let mutable ex = exp
let mutable res = 1
let mutable n = num
if ex < 1 then
ex <- -ex
n <- 1 / n
while ex > 1 do
if (ex % 2 = 1) then (* still have a bug here *)
res <- res * n
n <- n * n
exp >>> 1 (* <--- this is not a variable assignment *)
res * n
We still have a bug, although I think F# is reporting the error in the wrong place. The expression exp >>> 1
returns an int, it does not assign any variables, so its not equivalent to your original C# code. I think you meant to use the ex
variable instead. We can fix your code as follows:
let FastPow2 num exp =
if exp = 0 then 1
else
let mutable ex = exp
let mutable res = 1
let mutable n = num
if ex < 1 then
ex <- -ex
n <- 1 / n
while ex > 1 do
if (ex % 2 = 1) then
res <- res * n
n <- n * n
ex <- ex >>> 1
res * n
Now your function is fixed, but its really really ugly. Lets convert it to more idiomatic F#. You can replace the if statement with pattern matching, and replace the while loop with recursion:
let FastPow2 num exp =
match exp with
| 0 -> 1
| _ ->
let rec loop ex res n =
if ex > 1 then
let newRes = if ex % 2 = 1 then res * n else res
loop (ex >>> 1) newRes (n * n)
else res * n
let ex, n = if exp < 1 then (-exp, 1 / num) else (exp, num)
loop ex 1 n
Much better! Theres still some more room to beautify this function, but you get the idea :)
Upvotes: 8
Reputation: 13582
It means that after then
there should be some expression, but you have integer value. You cannot jump out from the middle of the function.
Edit
"If" didn't work because of
ex >>> 1
should be
ex <- ex >>> 1
Here's code that works:
let FastPow num exp =
let calcExp num exp =
let mutable res = 1.0
let mutable n = num
let mutable ex = exp
while ex > 1 do
if ((ex % 2) = 1) then
res <- res * n
n <- n * n
ex <- ex >>> 1
res * n
match exp with
| ex when ex = 0 -> 1.0
| ex when ex < 0 -> calcExp (1.0/num) -exp
| _ -> calcExp num exp
I just take out calculation as separate function, and at the end there is checking for arguments
Upvotes: 0
Reputation: 6437
This is about as close as you can get, as others have already said you can't jump out of the middle of a functional and there's one place were you don't update a variable (at the bottom of the while).
let FastPow num exp =
let mutable exp = exp
let mutable res = 1
let mutable n = num
match exp with
| O -> n <- num
| _ when exp < 1 ->
exp <- -exp
n <- 1 / n
| _ ->
while exp > 1 do
if (exp % 2 = 1) then
res <- res * n
n <- n * n
exp <- exp >>> 1
res * n
I could be more beautiful if it was written more functionally.
Upvotes: 1
Reputation: 3769
The problem is for an if statment to resolve to a value rather than unit, you need both the "then" part and the "else" part, both of which resolve to the same type.
For example:
let a = if true then 1;;
Will generate the same error - expression has type int but used with type unit.
However:
let a = if true then 1 else 0;;
Will evaluate to int without an error.
Upvotes: 3