Inspired by a tweet linked to me by a friend and a Haskell implementation by her for the same problem, I decided to try my hand at approximating the value of π using everything in the Haskell standard library I could find for the job. Here’s what I came up with:
module Pi where
import Data.List (genericLength)
import Control.Arrow (Arrow, (<<<), (***), arr)
import System.Random (newStdGen, randoms)
type Point a = (a, a)
chunk2 :: [a] -> [(a, a)]
chunk2 [] = []
chunk2 [_] = error "list of uneven length"
chunk2 (x:y:r) = (x, y) : chunk2 r
both :: Arrow arr => arr a b -> arr (a, a) (b, b)
both f = f *** f
unsplit :: Arrow arr => (a -> b -> c) -> arr (a, b) c
unsplit = arr . uncurry
randomFloats :: IO [Float]
randomFloats = randoms <$> newStdGen
randomPoints :: IO [Point Float]
randomPoints = chunk2 <$> randomFloats
isInUnitCircle :: (Floating a, Ord a) => Point a -> Bool
isInUnitCircle (x, y) = x' + y' < 0.25
where x' = (x - 0.5) ** 2
y' = (y - 0.5) ** 2
lengthRatio :: (Fractional c) => [b] -> [b] -> c
lengthRatio = curry (unsplit (/) <<< both genericLength)
approximatePi :: [Point Float] -> Float
approximatePi points = circleRatio * 4.0
where circlePoints = filter isInUnitCircle points
circleRatio = circlePoints `lengthRatio` points
main :: IO ()
main = do
putStrLn "How many points do you want to generate to approximate π?"
numPoints <- read <$> getLine
points <- take numPoints <$> randomPoints
print $ approximatePi points
I’m interested in a general review, but I’m especially curious about my use of arrows: is there a better way to write lengthRatio? Are anything like both and unsplit provided anywhere in the standard library? If not, do any packages help?