"fix' f = f (fix' f)" vs. "fix f = let x = f x in x"
groups.google.com/group/stanford-11au-cs240h/...Posted Nov 29, 2011, 10:28 AM UTC
Posted Nov 29, 2011, 10:28 AM UTC
The description of fix in Haskell that was the most intuitive to me was specifying it in terms of defined-ness.
-- Consider
f :: [Int] -> [Int]
f x = 0 : map (+1) x
-- a bottom
undefined :: forall a. a
undefined = error "undefined"
-- now see how it works on various levels of defined arguments
f undefined = 0 : map (+1) undefined
= 0 : undefined
f (f undefined) = 0 : map (+1) (f undefined)
= 0 : map (+1) (0 : undefined)
= 0 : 1 : undefined
f (f (f undefined)) = 0 : map (+1) (f (f undefined))
= 0 : map (+1) (0 : 1 : undefined)
= 0 : 1 : 2 : undefined
fix f = f (f (f (f (f (f (...
= 0 : 1 : 2 : 3 : 4 : ...
This works because f x, for any x, always agrees with f undefined in any place where the latter is defined. In a way, f undefined is a lower bound on the information returned by f.
But here you have two notions mixed up : fixed point and infinite lists. This complicates things imho. A fixed point is easier to explain on finite things with recursion. Infinite sequences clicked for me really only when I learned the notions of codata and corecursion ( http://blog.sigfpe.com/2007/07/data-and-codata.html )
I don't think this has anything to do with infinite lists; here's the gold standard example of fix: fact
factr :: (Int -> Int) -> (Int -> Int)
factr rec 1 = 1
factr rec n = n * rec (n-1)
fact = fix factr
factr undefined 3 = 3 * undefined 2
= undefined
factr (factr undefined) 3 = 3 * factr undefined 2
= 3 * (2 * undefined 1)
= undefined
factr (factr (factr undefined)) 3
= 3 * factr (factr undefined) 2
= 3 * (2 * factr undefined 1)
= 3 * (2 * 1)
= 6
Similarily to the infinite list example, we push the 'defined'-ness deeper into the computation. fix factr n = factr (factr (factr ...)) n gives you an arbitrary level of defined-ness, just like the previous example gave you an arbitrarily long list.
Ok, your method of pushing 'defined'-ness makes more sense here for me as the result is clearly defined and terminating.
clearly, the second one is better!
Could someone explain how first one actually working?
just apply the definition over and over again...
fix' f
f (fix' f)
f (f (fix' f))
f (f (f (fix' f)))
...
f (f (f (f (f (f (f ...
Well nothing could go wrong with that!
It leaks memory, but apart from that the first definition works just fine in Haskell.
[deleted]
That's not the point (see the link): they behave differently.
And besides, the second one is more readable.
Do they? By referential transparency they are the same thing, right? :)
"fix" just optimizes a bit for memory usage, but idealized machines have arbitrary amounts of memory.
By referential transparency, insertion sort and merge sort are the same thing, right?
Denotational semantics aren't the only thing that matters.
Possibly; there may be some "interesting" Ord instances that cause them to differ.
That said, my post was tongue in cheek. Of course it matters that they perform differently.