error:: String -> a
by giving error a string it can make any type at all. It does so by stopping the execution of the program the very moment that value is actually needed.
Your function f on the other hand throws out its input without considering its value and so goes on its merry way without ever finding out what its input was.
Interestingly, there is no f you could write that actually not work the way f does because it is not possible to write a function that inspects the value of type of Void. Void is uninhabited so no function can ever force its evaluation.