I'm trying to convert some Haskell code to F# but I'm having some trouble since Haskell is lazy by default and F# is not. I'm also still learning my way around F#. Below is a polymorphic cosine function in Haskell with pretty good performance. I want to try and keep the same or better performance parameters in F#. I would like to see a F# List version and a F# Seq version since the Seq version would be more like the lazy Haskell but the List version would probably perform better. Thanks for any help.
Efficiency: number of arithmetic operations used proportional to number of terms in series
Space: uses constant space, independent of number of terms
takeThemTwoByTwo xs =
takeWhile (not . null) [take 2 ys | ys <- iterate (drop 2) xs]
products xss = [product xs | xs <- xss]
pairDifferences xs =
[foldr (-) 0 adjacentPair | adjacentPair <- takeThemTwoByTwo xs]
harmonics x = [x/(fromIntegral k) | k <- [1 ..]]
cosineTerms = scanl (*) 1 . products . takeThemTwoByTwo . harmonics
cosine = foldl (+) 0 . pairDifferences .
take numberOfTerms . cosineTerms
Here is my attempt in case you're lazy to read:
let harmonics x =
Seq.initInfinite(fun i -> - x*x/(float ((2*i+1)*(2*i+2))))
let cosineTerms = Seq.scan (*) 1.0 << harmonics
let cosine numberOfTerms = Seq.sum << Seq.take numberOfTerms << cosineTerms
I have a hard time finding out that you're calculating cosine
in radian using Taylor series:
cosine(x) = 1 - x 2 /2! + x 4 /4! - x 6 /6! + ...
Let me describe what you're doing:
x/k
where k
is an integer starting from 1
. Split above sequence into chunks of two and scan by multiplying with a seed of 1
to have a sequence of x 2 /((2k-1)*(2k)) (with an exception of 1
at the beginning).
Split the new sequence into blocks of two again to have differences in the form of x 4k-4 /((4k-4)!) - x 4k-2 /((4k-2)!) and sum all of them to get final result.
Because it's likely to be inefficient to split sequences in F# and takeThemTwoByTwo
function is not essential, I chose another approach:
k
is an integer starting from 1
. 1
; we get a sequence of (-1) k * x 2k /((2k)!). Above program is a direct translation of my description, succinct and simple. Computing cosine
with numberOfTerms = 200000
iterations takes 0.15 seconds on my machine; I suppose it is efficient enough for your purpose.
Furthermore, a List
version should be easy to translate from this one.
UPDATE:
Ok, my fault was to underestimate the polymorphism part of the question. I focused more on the performance part. Here is a polymorphic version (keeping closely to the float
version):
let inline cosine n (x: ^a) =
let one: ^a = LanguagePrimitives.GenericOne
Seq.initInfinite(fun i -> LanguagePrimitives.DivideByInt (- x*x) ((2*i+1)*(2*i+2)))
|> Seq.scan (*) one
|> Seq.take n
|> Seq.sum
Seq.initInfinite
is less powerful than Seq.unfold
in @kvb 's answer. I keep it to make things simple because n
is in int
range anyway.
Pad's answer is good, but not polymorphic. In general, it's significantly less common to create such definitions in F# than in Haskell (and a bit of a pain). Here's one approach:
module NumericLiteralG =
let inline FromZero() = LanguagePrimitives.GenericZero
let inline FromOne() = LanguagePrimitives.GenericOne
module ConstrainedOps =
let inline (~-) (x:^a) : ^a = -x
let inline (+) (x:^a) (y:^a) : ^a = x + y
let inline (*) (x:^a) (y:^a) : ^a = x * y
let inline (/) (x:^a) (y:^a) : ^a = x / y
open ConstrainedOps
let inline cosine n x =
let two = 1G + 1G
Seq.unfold (fun (twoIp1, t) -> Some(t, (twoIp1+two, -t*x*x/(twoIp1*(twoIp1+1G))))) (1G,1G)
|> Seq.take n
|> Seq.sum
As Pad wrote, this appears to be the Taylor series expansion of cos( x ) about x =0:
cosine(x) = 1 - x²/2! + x⁴/4! - x⁶/6! + ...
So your question is an XY question: you presented a solution rather than posing the problem. Presenting the problem instead makes it much easier to solve it differently.
Let's start by writing a float
-specific version in F#:
let cosine n x =
let rec loop i q t c =
if i=n then c else
loop (i + 1) (q + 10 + 8*i) (-t * x * x / float q) (c + t)
loop 0 2 1.0 0.0
For example, we can compute 1M terms of the expansion of x=0.1:
cosine 1000000 0.1
The best way to make this polymorphic in F# is to parameterize the function over the operators it uses and mark it as inline
in order to remove the performance overhead of this parameterization:
let inline cosine zero one ofInt ( ~-. ) ( +. ) ( *. ) ( /. ) n x =
let rec loop i q t c =
if i=n then c else
loop (i + 1) (q + 10 + 8*i) (-.t *. x *. x /. ofInt q) (c +. t)
loop 0 2 one zero
Now we can compute 1M terms using float
like this, which is just as fast as before:
cosine 0.0 1.0 float ( ~- ) (+) (*) (/) 1000000 0.1
But we can also do single-precision float
:
cosine 0.0f 1.0f float32 ( ~- ) (+) (*) (/) 1000000 0.1f
And arbitrary-precision rational:
cosine 0N 1N BigNum.FromInt (~-) (+) (*) (/) 10 (1N / 10N)
And even symbolic:
type Expr =
| Int of int
| Var of string
| Add of Expr * Expr
| Mul of Expr * Expr
| Pow of Expr * Expr
static member (~-) f = Mul(Int -1, f)
static member (+) (f, g) = Add(f, g)
static member (*) (f, g) = Mul(f, g)
static member (/) (f, g) = Mul(f, Pow(g, Int -1))
cosine (Int 0) (Int 1) Int (~-) (+) (*) (/) 3 (Var "x")
To make it faster, hoist the common subexpression -x*x
out of loop
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.