Whoever skipped those explicit arguments just made it unnecessarily harder on yourself to learn this stuff. It's totally superficial. Adding the explicit arguments, as specified by the type signature, gives us
squareOn :: (Eq a, Num a) => [a] -> a -> a
squareOn = foldr (\x acc y -> if y == x then x*x else acc y) id
squareOn xs = foldr (\x acc y -> if y == x then x*x else acc y) id xs
squareOn xs y = foldr (\x acc y -> if y == x then x*x else acc y) id xs y
squareOn xs y = foldr g id xs y where { g x acc y | y == x = x*x
| otherwise = acc y }
squareOn xs y = (case xs of {
[] -> id ;
(x:xs2) -> g x (foldr g id xs2)
}) y where { g x acc y | y == x = x*x
| otherwise = acc y }
Now we can see everything in play here, as opposed to having to keep it all in mind. There is playing chess, and then there's playing blindfold chess, and why play it blindfolded if you can just see?
So now it becomes obvious that passing that y
around(*) from call to call unchanged actually has no purpose here, because it is the same y
, and it is already in scope:
squareOn xs y = (case xs of {
[] -> y ;
(x:xs2) -> g x (foldr g y xs2)
}) where { g x acc | y == x = x*x
| otherwise = acc }
which simplifies back as just
squareOn xs y = foldr g y xs where { g x acc | y == x = x*x
| otherwise = acc }
{- cf.
squareOn xs y = foldr g id xs y where { g x acc y | y == x = x*x
| otherwise = acc y } -}
And to be pointlessly short and pointfree, like your original code,
squareOn = flip (foldr g) where { g x acc | y == x = x*x
| otherwise = acc }
Or it could be simplified to
squareOn xs y = case xs of {
[] -> y ;
(x:_) | y == x -> x*x ;
(_:xs2) -> squareOn xs2 y }
and further to a worker/wrapper with nested unary worker, whichever is clearer for you.
Passing the unchanged quantity around to have it in scope is only really needed in languages without nested scope, like Prolog.
(*)(so that explanation in full, which you asked for, about how this technique works, is actually in the linked answer).