While I know precisely what you are saying (have been learning scheme theory this summer), this is hardly an accessible example to
pure math undergrads, let alone someone who's attempting to learn some functional programming, with no heavy experience with abstract algebra.
First of all, why use Spec? Use ideals/varieties, it contains roughly the same data, while being way better to intuit. I'll put my money where my mouth is, and give it a shot.
Say we have some collection of points in |R^2, and we want to find equations which define this set. We do this by creating a function
f: points in |R^2 -> set of polynomials whose common zeros are the points.
For example,
1. f(unit circle at the origin) = { x^2 + y^2 - 1 }, because all points on the unit circle satisfy x^2 + y^2 - 1 = 0.
2. f(the full space |R^2) = { 0 } because the constant zero / the zero polynomial is zero on the entire plane.
3. f(empty set) = { 1 } because the polynomial/constant 1 is Nonzero on the entire plane.
4. f({all points on either the X axis or the y axis }) = { xy }, because points on either the X axis or the y axis satisfy X = 0 or y = 0, which is implied by xy = 0
5. The intersection of the XY axes and the unit circle, which are the points { (+-1, +-1) } is cut out by the common roots of the polynomials { XY, x^2 + y^2 - 1 }.
After some rumination, one will notice that as we increase the number of "points", we will need to decrease the number of polynomials: each polynomial is a constraint, so having more polynomials is having less points that satisfy these constrains.
This is the crux of the contravariance between algebra and geometry: geometry describes the thing in itself, algebra describes how to get at the thing using constraints. These will always be dual to each other.
How did I do at an attempt at an explanation?