It might be a good idea to not translate it straight to Haskell but rather first to C++, which already allows to you structure it in a much more functional way.
First thing, as Cirdec commented, this function doesn't really take perim1 as arguments – those are “output arguments” as Fortran people would say, i.e. they're really results. Also, the v parameter seems to be basically just length of the input array. So in C++ you can reduce it to:
std::pair<double, double> calcPerim(std::vector <point> polycake, int y){
double perim1 = 0, perim2 = 0;
...
return std::make_pair(perim1, perim2);
}
Now, you have this mutating for loop. In a functional language, the general approach would be to replace that with recursion. For this, you need to make all mutable-state variables function parameters. That includes i, index, points and the perim accumulators (so they're back, in a way... but now as input arguments). You don't need next (which is anyways re-computed from scratch in each iteration).
std::pair<double, double> calcPerim_rec
( std::vector<point> polycake, int y
, int i, int index, std::array<point,2> points
, double perim1Acc, double perim2Acc ){
...
}
...to be used by
std::pair<double, double> calcPerim(std::vector<point> polycake, int y){
return calcPerim_rec(polycake, y, 0, 0, {}, 0, 0);
}
The recursive function looks very similar to your original loop body; you just need to phrase the end condition:
std::pair<double, double> calcPerim_rec
( std::vector<point> polycake, int y
, int i, int index, std::array<point,2> points
, double perim1Acc, double perim2Acc ){
if (i < polycake.length()) {
int next = (i + 1) % polycake.length();
if(polycake[i].y < y && polycake[next].y < y)
perim1Acc += distance(polycake[i], polycake[next]);
else if(polycake[i].y > y && polycake[next].y > y)
perim2Acc += distance(polycake[i], polycake[next]);
else
{
points[index] = intersectPoint(polycake[i], polycake[next], y);
if(polycake[i].y < y)
{
perim1Acc += distance(polycake[i], points[index]);
perim2Acc += distance(polycake[next],points[index]);
}
else
{
perim2Acc += distance(polycake[i], points[index]);
perim1Acc += distance(polycake[next],points[index]);
}
++index;
}
++i;
return calcPerim_rec
( polycake, y, i, index, points, perim1Acc, perim2Acc );
} else {
perim1Acc += distance(points[0], points[1]);
perim2Acc += distance(points[0], points[1]);
return std::make_pair(perim1Acc, perim2Acc);
}
}
There's still quite a bit of mutability going on, but we've already encapsulated it to happen all on local variables of the recursion function call, instead of variables lying around during the loop execution. And each of these variables is only updated once, followed by the recursive call, so you can just skip the mutation and simply pass a value plus update to the recursive call:
std::pair<double, double> calcPerim_rec
( std::vector<point> polycake, int y
, int i, int index, std::array<point,2> points
, double perim1Acc, double perim2Acc ){
if (i < polycake.length()) {
int next = (i + 1) % polycake.length();
if(polycake[i].y < y && polycake[next].y < y)
return calcPerim_rec
( polycake, y, i+1, index, points
, perim1Acc + distance(polycake[i], polycake[next])
, perim2Acc
);
else if(polycake[i].y > y && polycake[next].y > y)
return calcPerim_rec
( polycake, y, i+1, index, points
, perim1Acc
, perim2Acc + distance(polycake[i], polycake[next])
);
else
{
points[index] = intersectPoint(polycake[i], polycake[next], y);
if(polycake[i].y < y)
{
return calcPerim_rec
( polycake, y, i+1, index+1
, points
, perim1Acc + distance(polycake[i], points[index])
, perim2Acc + distance(polycake[next],points[index])
);
}
else
{
return calcPerim_rec
( polycake, y, i+1, index+1
, points
, perim1Acc + distance(polycake[i], points[index])
, perim2Acc + distance(polycake[next],points[index])
);
}
}
} else {
return std::make_pair( perim1Acc + distance(points[0], points[1])
, perim2Acc + distance(points[0], points[1]) );
}
}
Well, quite a bit of awkward passing-on of parameters, and we still have a mutation of points – but essentially, the code can now be translated to Haskell.
import Data.Vector (Vector, (!), length) as V
calcPerim_rec :: Vector Point -> Int -> Int -> Int -> Int -> [Point] -> (Double, Double) -> (Double, Double)
calcPerim_rec polycake y i index points (perim1Acc, perim2Acc)
= if i < V.length polycake
then
let next = (i + 1) `mod` V.length polycake
in if yCoord (polycake!i) < y && yCoord (polycake!next) < y
then calcPerim_rec polycake v y (i+1) index points
(perim1Acc + distance (polycake!i) (polycake!next)
perim2Acc
else
if yCoord (polycake!i) > y && yCoord (polycake!next) > y)
then calcPerim_rec polycake v y (i+1) index points
perim1Acc
(perim2Acc + distance (polycake!i) (polycake!next))
else
let points' = points ++ [intersectPoint (polycake!i) (polycake!next) y]
in if yCoord (polycake!i) < y
then calcPerim_rec polycake v y (i+1) (index+1)
points'
(perim1Acc + distance (polycake!i) (points!!index))
(perim2Acc + distance (polycake!next) (points!!index))
else calcPerim_rec polycake v y (i+1) (index+1)
points'
(perim1Acc + distance (polycake!i) points!!index))
(perim2Acc + distance (polycake!next) points!!index))
else ( perim1Acc + distance (points!!0) (points!!1)
, perim2Acc + distance (points!!0) (points!!1) )
There's a lot here that could be stylistically improved, but it should in essence work.
A good first thing to actually make it idiomatic is to try and get rid of indices. Indices are strongly eschewed in Haskell, and can often be avoided when you properly work with lists instead of arrays.
*perim1or*perim2is obliderated when they are set to0.0.zip polycake (drop 1 polycake)and loop over that using some fold. Perhaps also usecycle polycaketo make the access circular, andtake vto stop the iteration.