I'd advocate for this signature:
piApprox :: (Fractional r) => Int -> r
The reason being, the "precision" parameter doesn't have any particular "value" meaning, it's just a counter of how many steps you're willing to let the function run. (A better approach might be specifying what deviation to the true value π you want to allow instead of the evaluation depth, but that's more complicated.)
Next, the point where your currect implementation clashes is actually (-1)^k
(it requires Integral
because the exponentiation is implemented by recursed multiplication). Yes, that's the usual way to denote an alternating sign in maths and science, but if you think about it it's a pretty bad way. You're not really interested in powers here, just in the sign-alternation, and that's far more naturally achieved with cycle [1, -1]
.
For the multiplication it's different, that doesn't need Integral
at all but requires that both arguments have the same type. The natural way to achieve this is, use a Fractional
variable right away, rather than converting from a integral one! So instead of [0..n]
, you could use [0 .. fromIntegral n]
. Just one conversion instead of one at each step.
Actually though, it's better not to bound the indices at all! Since this is Haskell, you can define the list as infinite (like what cycle
also does). Of course you can't sum an infinite list, but you can simply trim it down right before doing so:
piApprox :: (Fractional r) => Int -> r
piApprox n = 4 * sum (take n [ σ / (2*k + 1) | (σ,k) <- zip (cycle [-1,1]) [0..] ])
Or, perhaps nicer written with the ParallelListComprehensions
extension:
piApprox n = (4 *) . sum $
take n [ σ / (2*k + 1) | σ <- cycle [1, -1]
| k <- [0..]
]
This does one step less than your implementation because take n [0..]
is equivalent to [0..n-1]
. I suppose this doesn't matter too much, otherwise it's trivial to fix.
Finally: I assume you're aware that this formula is pretty bad in terms of convergence speed!