>>>flip fix (0 :: Int) (\\a b -> putStrLn \"abc\")
Output: \"abc\"
This is a simplified version of using flip fix
.
I saw t
fix
is the fixed-point operator. As you probably know from it's definition, it computes the fixed point of a function. This means, for a given function f
, it searches for a value x
such that f x == x
.
We can view x
as the result of infinite term f (f (f ... ) ...))
. Obviously, since it is infinite, adding f
in front of it doesn't change it, so f x
will be the same as x
. Of course, we cannot express an infinite term, but we can define fix
as fix f = f (fix f)
, which expresses the idea.
Will it ever terminate? Yes, it will, but only because Haskell is a lazy language. If f
doesn't need its argument, it will not evaluate it, so the computation will terminate, it won't loop forever. If we call fix
on a function that always uses its argument (it is strict), it will never terminate. So some functions have a fixed point, some don't. And Haskell's lazy evaluation ensures that we compute it, if it exists.
fix
useful?It expresses recursion. Any recursive function can be expressed using fix
, without any additional recursion. So fix
is a very powerful tool! Let's say we have
fact :: Int -> Int
fact 0 = 1
fact n = n * fact (n - 1)
we can eliminate recursion using fix
as follows:
fact :: Int -> Int
fact = fix fact'
where
fact' :: (Int -> Int) -> Int -> Int
fact' _ 0 = 1
fact' r n = n * r (n - 1)
Here, fact'
isn't recursive. The recursion has been moved into fix
. The idea is that fact'
accepts as its first argument a function that it will use for a recursive call, if it needs to. If you expand fix fact'
using the definition of fix
, you'll see that it does the same as the original fact
.
So you could have a language that only has a primitive fix
operator and otherwise doesn't permit any recursive definitions, and you could express everything you can with recursive definitions.
Let's view flip fix (0 :: Int) (\a b -> putStrLn "abc")
, it is just fix (\a b -> putStrLn "abc") (0 :: Int)
. Now let's evaluate:
fix (\a b -> putStrLn "abc") =
(\a b -> putStrLn "abc") (fix (\a b -> putStrLn "abc")) =
\b -> putStrLn "abc"
So the whole expression evaluates to (\b -> putStrLn "abc") (0 :: Int)
which is just putStrLn "abc"
. Because function \a b -> putStrLn "abc"
ignores its first argument, fix
never recurses. It's actually used here only to obfuscate the code.