Unary map is map but binary map is zipWith. So what's to zip together? The two copies of the same list, shifted by one position one against the other:
calc :: Int -> Int -> Float
g :: [Int] -> [Float]
g xs = zipWith calc xs (drop 1 xs)
This is all. The zipping automatically stops when the shorter sequence runs out.
zipWith foo applies foo to each pair of arguments, each argument coming from the corresponding list, one element after the other. It could be implemented using zip as
zipWith foo xs ys = map (\ (x,y) -> foo x y) $ zip xs ys
= [ foo x y | (x,y) <- zip xs ys ]
but it already does this by itself.
As an illustration, drop 1 [1,2,3] == [2,3] and this means that
zipWith calc [1, 2, 3] (drop 1 [1, 2, 3]) ==
zipWith calc [1, 2, 3]
[2, 3 ] ==
[calc 1
2,
calc 2
3]
You wanted to use indices. Indices are practically never used in Haskell with lists. We instead achieve the same effect by structural jiggling, and then when we advance along the two lists one element at a time, each operation is O(1) because the next element is already at the top of the advanced list. Whereas with the indices we'd have to traverse the same list from the top anew each time, leading to O(k) time per each one (kth) element, i.e. quadratic behavior overall.
update: taking the zipping approach leads to a possibility of code fusion,
g2 xs = zipWith calc xs (drop 1 xs)
= [calc x y | (x,y) <- zip xs (drop 1 xs)]
= [calc x y | (x:y:_) <- tails xs] -- tails ~= iterate (drop 1)
because why build a new structure to house the two consecutive elements when they already appear in a structure ... the original list structure.
Interestingly, Common Lisp has this special kind of map, a map on tails, as part of the language. While the regular map is known there as mapcar, the map on tails is known there as maplist (it's a map on non-empty tails, to be precise).