I am trying to learn Haskell, this is my very first approach at functional programming. I'm having some trouble creating a function that takes a number as input and prints that number one by one recursively, until 0.
printDescending n = if n > 0
then printDescending n - 1 return n
else return n - 1
I want to be able to do printDescending 20 and have it output 20, 19, 18...2, 1, 0. But I'm getting this error:
> • Non type-variable argument
> in the constraint: Num ((a -> m a) -> a1 -> m1 a1)
> (Use FlexibleContexts to permit this)
> • When checking the inferred type
> printDescending :: forall (m :: * -> *) a (m1 :: * -> *) a1.
> (Ord a1, Num ((a -> m a) -> a1 -> m1 a1), Num (m1 a1), Num a1,
> Monad m1, Monad m) =>
> a1 -> m1 a1 Failed, modules loaded: none.
I think it may be my syntax. Anyone have some insight?
You have shown:
printDescending n = if n > 0
then printDescending n - 1 return n
else return n - 1
Lets first look at how this is parsed:
printDescending n = if n > 0
then (printDescending n) - (1 return n)
else (return n) - 1
Probably not what you intended, huh? Notice the infix operators, such as - bind less tightly than function application. Also see how 'return' is not special - it's also just function application. Finally, you didn't actually include a call to any sort of print command so no matter what language you are using I wouldn't expect this to function.
Fixing these issues, let's first provide a type signature (which is somewhat like a function prototype in C):
printDescending :: Int -> IO ()
So printDecending accepts one argument, an Int, and does some IO. the () is called "unit" and for the first lesson of a programmer who knows C you should be able to mentally think of that as void and be OK.
Now how about the body? Well your if statement was fine:
printDescending n =
if n > 0
then ...
else...
The else statement was a little weird. Even fixing the parsing, why would you want 0-1? Let's just return unit:
printDescending n =
if n > 0
then ...
else return ()
Now for the then branch you want two things really. 1. print the value 2. recursively call printDecending on the next smallest value. There is one bit of notation, do, that we'll use to sequence two IO operations but beyond that these two tasks translate to commands directly:
printDescending n =
if n > 0
then do print n
printDescending (n - 1)
else return ()
Now lets do one more step. Since this isn't C/Java/etc and is a functional language then people will expect a declarative style. To that end, let's declare printDescending using guards instead of an if statement:
printDescending n
| n > 0 = do print n
printDescending (n-1)
| otherwise = return ()
Alternatives
Now I'm going to present some alternatives, just for exemplification.
If you don't care about negative values then we could just terminate on zero with something like:
printDescending 0 = return ()
printDescending n =
do print n
printDescending (n-1)
Or we can use list comprehension to build a list of the values then perform print on each element of the list. We can do this with forM_ (see also the more idiomatic mapM_) which executes an operation on each element of the list and throws away the results, returning unit.
printDescending n = forM_ [n,n-1..1] print
Finally, if you want to have more rapid exchanges then consider joining the #haskell channel on irc.freenode.net. There's a great group there and it's where many of the Haskell programmers I know got their start.
In addition to Thomas' detailed answer, I get the feeling that the following is what you were going for:
printDescending :: Integer -> IO ()
printDescending n = do
print n
if n > 0
then printDescending (n - 1)
else return ()
Admittedly, the return () may seem strange at first.
A more functional approach might be something like this:
printDescending :: Integer -> IO ()
printDescending n = mapM_ print [n, (n-1) .. 0]
A quick tip: I'd recommend adding the function signature (if you know what it is) as it will help guide the compiler's error messages.
Related
I have a few questions regarding the Just syntax in Haskell.
When question arose when I was experimenting with different ways to write a function to calculate binomial coefficients.
Consider the function:
binom :: Integer -> Integer -> Maybe Integer
binom n k | n < k = Nothing
binom n k | k == 0 = Just 1
binom n k | n == k = Just 1
binom n k | otherwise = let
Just x = (binom (n-1) (k-1))
Just y = (binom (n-1) k)
in
Just (x + y)
When I try to write the otherwise case without the let..in block without the let..in block like so:
binom n k | otherwise = (binom (n-1) (k-1)) + (binom (n-1) k)
I am faced with a compilation error No instance for (Num (Maybe Integer)) arising from a use of ‘+’. And so my first thought was that I was forgetting the Just syntax so I rewrote it as
binom n k | otherwise = Just ((binom (n-1) (k-1)) + (binom (n-1) k))
I am faced with an error even more confusing:
Couldn't match type ‘Maybe Integer’ with ‘Integer’
Expected: Maybe Integer
Actual: Maybe (Maybe Integer)
If I add Just before the binom calls, the error just compounds:
Couldn't match type ‘Maybe (Maybe Integer)’ with ‘Integer’
Expected: Maybe Integer
Actual: Maybe (Maybe (Maybe Integer))
Furthermore, if I write:
Just x = binom 3 2
y = binom 3 2
x will have the value 3 and y will have the value Just 3.
So my questions are:
Why does the syntax requite the let..in block to compile properly?
In the function, why does Just add the Maybe type when I don't use let..in?
Contrarily, why does using Just outside of the function remove the Just from the value if it's type is Just :: a -> Maybe a
Bonus question, but unrelated:
When I declare the function without the type the compiler infers the type binom :: (Ord a1, Num a2, Num a1) => a1 -> a1 -> Maybe a2. Now I mostly understand what is happening here, but I don't see why a1 has two types.
Your question demonstrates a few ways you may have got confused about what is going on.
Firstly, Just is not any kind of syntax - it's just a data constructor (and therefore also a function) provided by the standard library. The reasons your failing attempts didn't compile are therefore not due to any syntax mishaps (the compiler would report a "parse error" in this case), but - as it actually reports - type errors. In other words the compiler is able to parse the code to make sense of it, but then when checking the types, realises something is up.
So to expand on your failing attempts, #1 was this:
binom n k | otherwise = Just ((binom (n-1) (k-1)) + (binom (n-1) k))
for which the reported error was
No instance for (Num (Maybe Integer)) arising from a use of ‘+’
This is because you were trying to add the results of 2 calls to binom - which according to your type declaration, are values of type Maybe Integer. And Haskell doesn't by default know how to add two Maybe Integer values (what would Just 2 + Nothing be?), so this doesn't work. You would need to - as you eventually do with your successful attempt - unwrap the underlying Integer values (assuming they exist! I'll come back to this later), add those up, and then wrap the resulting sum in a Just.
I won't dwell on the other failing attempts, but hopefully you can see that, in various ways, the types also fail to match up here too, in the ways described by the compiler. In Haskell you really have to understand the types, and just flinging various bits of syntax and function calls about in the wild hope that the thing will finally compile is a recipe for frustration and lack of success!
So to your explicit questions:
Why does the syntax requite the let..in block to compile properly?
It doesn't. It just needs the types to match everywhere. The version you ended up with:
let
Just x = (binom (n-1) (k-1))
Just y = (binom (n-1) k)
in
Just (x + y)
is fine (from the type-checking point of view, anyway!) because you're doing as I previously described - extracting the underlying values from the Just wrapper (these are x and y), adding them up and rewrapping them.
But this approach is flawed. For one thing, it's boilerplate - a lot of code to write and try to understand if you're seeing it for the first time, when the underlying pattern is really simple: "unwrap the values, add them together, then rewrap". So there should be a simpler, more understandable, way to do this. And there is, using the methods of the Applicative typeclass - of which the Maybe type is a member.
Experienced Haskellers would write the above in one of two ways. Either:
binom n k | otherwise = liftA2 (+) (binom (n-1) (k-1)) (binom (n-1) k)
or
binom n k | otherwise = (+) <$> binom (n-1) (k-1) <*> binom (n-1) k
(the latter being in what is called the "applicative style" - if you're unfamiliar with Applicative functors there's a great introduction in Learn You a Haskell here. )
And there's another advantage of doing this compared to your way, besides the avoidance of boilerplate code. Your pattern matches in the let... in expression assume that the results of binom (n-1) (k-1) and so on are of the form Just x. But they could also be Nothing - in which case your program will crash at runtime! And exactly this will indeed happen in your case, as #chepner describes in his answer.
Using liftA2 or <*> will, due to how the Applicative instance is implemented for Maybe, avoid a crash by simply giving you Nothing as soon as one of the things you're trying to add is Nothing. (And this in turn means your function will always return Nothing - I'll leave it to you to figure out how to fix it!)
I'm not sure I really understand your questions #2 and #3, so I won't address those directly - but I hope this has given you some increased understanding of how to work with Maybe in Haskell. Finally for your last question, although it's quite unrelated: "I don't see why a1 has two types" - it doesn't. a1 denotes a single type, because it's a single type variable. You're presumably referring to the fact it has two constraints - here Ord a1 and Num a1. Ord and Num here are typeclasses - like Applicative is that I mentioned earlier (albeit Ord and Num are simpler typeclasses). If you don't know what a typeclass is I recommend reading an introductory source, like Learn You a Haskell, before continuing much further with the language - but in short it's a bit like an interface, saying that the type must implement certain functions. Concretely, Ord says the type must implement order comparisons - you need that here because you've used the < operator - while Num says you can do numeric things with it, like addition. So that type signature just makes explicit what is implicit in your function definition - the values you use this function on must be of a type that implements both order comparison and numeric operations.
binom n k | otherwise = (binom (n-1) (k-1)) + (binom (n-1) k)
You can't add two Maybe values, but you can make use of the Functor instance to add the values already wrapped in Just.
binom n k | otherwise = fmap (+) (binom (n-1) (k-1)) (binom (n-1) k)
This doesn't quite work, as eventually the recursive calls will return Nothing, and fmap (+) x y == Nothing if either x or y is Nothing. The solution is to treat two different occurrences of n < k differently.
An "initial" use can return Nothing
A "recursive" use can simply return 0, since x + 0 == x.
binom will be implemented in terms of a helper that is guaranteed to receive arguments such that n >= k.
binom :: Integer -> Integer -> Maybe Integer
binom n k | n < k = Nothing
| otherwise = Just (binom' n k)
where binom' n 0 = 1
binom' n k | n == k = 1
| otherwise = binom' (n-1) (k-1) + binom' (n-1) k
This question has received excellent answers. However, I think it is worth mentioning that you can also use a monadic do construct, like the one normally used for the “main program” of a Haskell application.
The main program generally uses a do construct within the IO monad. Here, you would use a do construct within the Maybe monad.
Your binom function can be modified like this:
binom :: Integer -> Integer -> Maybe Integer
binom n k | n < 0 = Nothing -- added for completeness
binom n k | k < 0 = Nothing -- added for completeness
binom n k | n < k = Nothing
binom n k | k == 0 = Just 1
binom n k | n == k = Just 1
binom n k | otherwise = do -- monadic do construct, within the Maybe monad
x <- (binom (n-1) (k-1))
y <- (binom (n-1) k)
return (x+y)
main :: IO ()
main = do -- classic monadic do construct, within the IO monad
putStrLn "Hello impure world !"
putStrLn $ show (binom 6 3)
If a single <- extractor fails, the whole result is Nothing.
Please recall that in that context, return is just an ordinary function, with type signature:
return :: Monad m => a -> m a
Unlike in most imperative languages, return is not a keyword, and is not part of control flow.
A key concern is that if you have many quantities that can become Nothing, the do construct looks more scalable, that is, it can become more readable than pattern matching or lift'ing functions. More details about using the Maybe monad in the online Real World Haskell book.
Note that the Haskell library provides not only liftA2, as mentioned in Robin Zigmond's answer, but also other lift'ing functions up to lift6.
Interactive testing:
You can test the thing under the ghci interpreter, like this:
$ ghci
GHCi, version 8.8.4: https://www.haskell.org/ghc/ :? for help
λ>
λ> do { n1 <- (Just 3) ; n2 <- (Just 42); return (n1+n2) ; }
Just 45
λ>
λ> do { n1 <- (Just 3) ; n2 <- (Just 42); n3 <- Nothing ; return (n1+n2+n3) ; }
Nothing
λ>
The exact semantics depend on the sort of monad involved. If you use the list monad, you get a Cartesian product of the lists you're extracting from:
λ>
λ> do { n1 <- [1,2,3] ; n2 <- [7,8,9]; return (n1,n2) ; }
[(1,7),(1,8),(1,9),(2,7),(2,8),(2,9),(3,7),(3,8),(3,9)]
λ>
I have a recursive Haskell function that takes a number n and generates a list that is n long of squared numbers from 1 up.
The code works but instead of the list for n=3 being [9,4,1] I want it to be [1,4,9].
sqNList :: Int -> [Int]
sqNList 0 = []
sqNList n = n*n : sqNList (n-1)
I've tried swappig the : for a ++ but then the pattern matching doesn't work. I have only just started Haskell today so may be obvious
The best approach is to implement sqNList in terms of a helper function that counts from 1 up to n. That way you can just generate the list in the correct order in the first place.
sqNList :: Int -> [Int]
sqNList n = sqNListHelper 1 n
sqNListHelper :: Int -> Int -> [Int]
sqNListHelper current end = ...
There are a wide variety of more sophisticated approaches, but this is the easiest approach to debug/test interactively that also has you figuring out how to do everything yourself.
More sophisticated approaches can include list comprehensions or composing functions like map and enumFromTo to build up the logic from simpler pieces.
The easiest approach that consumes probably the least amount of memory is to work with an accumulator: a parameter you pass and update through every recursive call.
Right now you use n as an accumulator, and decrement that, but we can decide to use an accumulator i instead that starts at 1, and keeps incrementing:
helper :: Int -> Int -> [Int]
helper i n | i > n = []
| otherwise = i*i : helper (i+1) n
Now we of course have to call it with i = 1 ourselves, which is not ideal, but we can use a where clause that scopes the helper in the sqNList:
sqNList :: Int -> [Int]
sqNList n = helper 1 n
where helper :: Int -> Int -> [Int]
helper i n | i > n = []
| otherwise = i*i : helper (i+1) n
Now we can improve this a bit. For instanc there is no need to pass n through the helper calls, since it does not change, and is defined at the top level:
sqNList :: Int -> [Int]
sqNList n = helper 1
where helper :: Int -> [Int]
helper i | i > n = []
| otherwise = i*i : helper (i+1)
There is furthermore no need to only work with Ints: we can use any type a for which Num a and Ord a:
sqNList :: (Num a, Ord a) => a -> [a]
sqNList n = helper 1
where helper i | i > n = []
| otherwise = i*i : helper (i+1)
This is a more advanced technique, so you may want to just save this for future study.
Most recursive functions tend to use one of the same general forms of recursion, so knowing which higher-order function to use saves you the effort of reimplementing (possibly incorrectly) the recursion, and lets you focus on the work that is unique to your problem.
This particular type of recursion is captured by the unfoldr function, defined in Data.List, which lets you generate a (possibly infinite) list using a generator function and an initial seed value.
Its definition can be as simple as
unfoldr f x = case f x of
Nothing -> []
Just (new, next) = new : unfoldr f next
although the actual definition is slightly more complicated for efficiency.
Essentially, you just call the generator f on the seed value x. If the result is Nothing, return an empty list. Otherwise, build a list containing a result and the list produced by a recursive call with a new seed value.
Applying this to your problem, we produce a square and the next integer as long as the input is still small enough.
import Data.List (unfoldr)
sqNList n = unfoldr generator 1
where generator x = if x > n then Nothing else Just (x*x, x+1)
So for a simple example like sqNList 3, it proceeds as follows:
Call generator 1 and get back Just (1, 2); the accumulator is now [1].
Call generator 2 and get back Just (4, 3); the accumulator is now
[1,4].
Call generator 3 and get back Just (9, 4); the accumulator is now [1,4,9].
Call generator 4 and get back Nothing. Return the accumulator [1,4,9] as the result.
Note that you generate the infinite list of all squares simply by never returning Nothing:
allSquares = unfoldr (\x -> Just (x*x, x+1)) 1
Haskell, being lazy, only generates elements in the list as you need them, so you could also define sqNList by taking only the first n items of the infinite list.
sqNList n = take n (unfoldr (\x -> Just (x*x, x+1)) 1)
Seems a function should have a name to be recursive(in order to call itself), so how does a lambda function be recursive?
I searched wikipedia and it says this could be done by a "Y-combinator". I don't have much math theory backgroup, it just tell me that "Y-combinator" is discovered by Haskell himself. Called "fix" keyword in Haskell language, I tried:
Prelude> let fact = fix (\f n -> if n == 0 then 1 else n * (f (n-1)))
<interactive>:17:12: Not in scope: ‘fix’
Seems failed, but how to use "fix" keyword to do what I expected?
fix is not a keyword, it is a function defined in the Data.Function module. Therefore, to use it, you must import that module.
import Data.Function
or in GHCi:
:m Data.Function
fix is defined as:
fix :: (a -> a) -> a
fix f = let x = f x in x
so it expands into f (f (f (f ...))) i.e. an infinite sequence of nested applications of the input function f to some parameter x.
You can give your lambda function a top-level definition e.g.
factF f n = if n == 0 then 1 else n * f (n - 1)
or equivalently:
factF :: (Int -> Int) -> (Int -> Int)
factF f = \n -> if n == 0 then 1 else n * f (n - 1)
you can provide this function to fix to obtain a function (Int -> Int) i.e.
fact :: (Int -> Int)
fact = fix factF
if you expand this definition you get
factF (factF (factF (factF ...)))
=> \n -> if n == 0 then 1 else (factF (factF (factF ...)))
due to laziness, the repeated applications of factF are only evaluated if n /= 0 so the above function applied to 0 will evaluate immediately to 1.
You can see in this expansion that factF is being provided as an argument to itself which it then applies to smaller version of n. Since factN is the same as your lambda function, the same thing is happening there - the parameter f inside the lambda is the lambda itself which it can then call recursively.
I'm new to Haskell. It's very nice so far, but I'm running into copy-pasting for my QuickCheck properties, and I'd like to fix that.
Here's a made-up example:
prop_Myfunc :: [Int] -> (Int,Int) -> Bool
prop_Myfunc ints (i,j) = ints !! i == ints !! j
This won't work because QuickCheck generates negative numbers, so I get
*** Failed! (after 2 tests and 2 shrinks):
Exception:
Prelude.(!!): negative index
I've tried to google for solutions to this, and I've found e.g. NonNegative and ==>, but I don't understand how they work.
How can I restrict the above example so that i and j are never negative? And also, so that neither is too high? That is: 0 <= i,j < length ints
First, see this SO answer for an example of how to write a custom Gen ... function and how to use the forAll combinator.
And here is how to write a generator for a non-empty list and two valid non-negative indices into the list:
import Test.QuickCheck
genArgs :: Gen ( [Int], Int, Int )
genArgs = do
x <- arbitrary
xs <- arbitrary
let n = length xs
i <- choose (0,n)
j <- choose (0,n)
return ( (x:xs), i, j) -- return a non-empty list
test = quickCheck $ forAll genArgs $ \(xs,i,j) -> prop_myfunc xs (i,j)
Constraining wrappers (from Test.QuickCheck.Modifiers, if they aren't reexported implicitly) can be used in this way:
prop_Myfunc :: [Int] -> (NonNegative Int, NonNegative Int) -> Bool
prop_Myfunc ints (NonNegative i, NonNegative j) = ints !! i == ints !! j
You can treat SomeWrapper a as a with modified distribution. For example, NonNegative a ensures that a >= 0. After the wrapper was generated, the value can be get with pattern-matching or explicit accessor (getNonNegative in this case).
As for constraining the top margin of your indices, I think it's not possible with wrappers (it's impossible in the Haskkell type system to parameterise a type with the value, the list length in this case). However, with the ==> operator you can add an arbitrary boolean constraint for your test:
prop_Myfunc ints (NonNegative i, NonNegative j) = i < l && j < l ==> ints !! i == ints !! j where
l = length ints
It works in other way: when the condition isn't true, it simply discards the current test case. But be careful: if there are too many thrown cases (the condition is too restrictive), the test becomes much less useful. A „lossless“ behaviour can be often achieved with shrinking test data, but it's a whole other topic.
I was in a similar situation as you and I finally found how to use ==> here: http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html, in the "Conditional Properties" section.
With your example, you'd have to replace Bool with Property and insert the requirements about your variables before the property test, as follows:
prop_Myfunc :: [Int] -> (Int,Int) -> Property
prop_Myfunc ints (i,j) = (i >= 0 && j >= 0) ==> ints !! i == ints !! j
(I haven't tested on this particular example, but it worked for me on a similar case.)
Note the type of ==>: (==>) :: Testable prop => Bool -> prop -> Property.
I was a bit confused by the documentation for fix (although I think I understand what it's supposed to do now), so I looked at the source code. That left me more confused:
fix :: (a -> a) -> a
fix f = let x = f x in x
How exactly does this return a fixed point?
I decided to try it out at the command line:
Prelude Data.Function> fix id
...
And it hangs there. Now to be fair, this is on my old macbook which is kind of slow. However, this function can't be too computationally expensive since anything passed in to id gives that same thing back (not to mention that it's eating up no CPU time). What am I doing wrong?
You are doing nothing wrong. fix id is an infinite loop.
When we say that fix returns the least fixed point of a function, we mean that in the domain theory sense. So fix (\x -> 2*x-1) is not going to return 1, because although 1 is a fixed point of that function, it is not the least one in the domain ordering.
I can't describe the domain ordering in a mere paragraph or two, so I will refer you to the domain theory link above. It is an excellent tutorial, easy to read, and quite enlightening. I highly recommend it.
For the view from 10,000 feet, fix is a higher-order function which encodes the idea of recursion. If you have the expression:
let x = 1:x in x
Which results in the infinite list [1,1..], you could say the same thing using fix:
fix (\x -> 1:x)
(Or simply fix (1:)), which says find me a fixed point of the (1:) function, IOW a value x such that x = 1:x... just like we defined above. As you can see from the definition, fix is nothing more than this idea -- recursion encapsulated into a function.
It is a truly general concept of recursion as well -- you can write any recursive function this way, including functions that use polymorphic recursion. So for example the typical fibonacci function:
fib n = if n < 2 then n else fib (n-1) + fib (n-2)
Can be written using fix this way:
fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))
Exercise: expand the definition of fix to show that these two definitions of fib are equivalent.
But for a full understanding, read about domain theory. It's really cool stuff.
I don't claim to understand this at all, but if this helps anyone...then yippee.
Consider the definition of fix. fix f = let x = f x in x. The mind-boggling part is that x is defined as f x. But think about it for a minute.
x = f x
Since x = f x, then we can substitute the value of x on the right hand side of that, right? So therefore...
x = f . f $ x -- or x = f (f x)
x = f . f . f $ x -- or x = f (f (f x))
x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.
So the trick is, in order to terminate, f has to generate some sort of structure, so that a later f can pattern match that structure and terminate the recursion, without actually caring about the full "value" of its parameter (?)
Unless, of course, you want to do something like create an infinite list, as luqui illustrated.
TomMD's factorial explanation is good. Fix's type signature is (a -> a) -> a. The type signature for (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) is (b -> b) -> b -> b, in other words, (b -> b) -> (b -> b). So we can say that a = (b -> b). That way, fix takes our function, which is a -> a, or really, (b -> b) -> (b -> b), and will return a result of type a, in other words, b -> b, in other words, another function!
Wait, I thought it was supposed to return a fixed point...not a function. Well it does, sort of (since functions are data). You can imagine that it gave us the definitive function for finding a factorial. We gave it a function that dind't know how to recurse (hence one of the parameters to it is a function used to recurse), and fix taught it how to recurse.
Remember how I said that f has to generate some sort of structure so that a later f can pattern match and terminate? Well that's not exactly right, I guess. TomMD illustrated how we can expand x to apply the function and step towards the base case. For his function, he used an if/then, and that is what causes termination. After repeated replacements, the in part of the whole definition of fix eventually stops being defined in terms of x and that is when it is computable and complete.
You need a way for the fixpoint to terminate. Expanding your example it's obvious it won't finish:
fix id
--> let x = id x in x
--> id x
--> id (id x)
--> id (id (id x))
--> ...
Here is a real example of me using fix (note I don't use fix often and was probably tired / not worrying about readable code when I wrote this):
(fix (\f h -> if (pred h) then f (mutate h) else h)) q
WTF, you say! Well, yes, but there are a few really useful points here. First of all, your first fix argument should usually be a function which is the 'recurse' case and the second argument is the data on which to act. Here is the same code as a named function:
getQ h
| pred h = getQ (mutate h)
| otherwise = h
If you're still confused then perhaps factorial will be an easier example:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120
Notice the evaluation:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 3 -->
let x = (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x in x 3 -->
let x = ... in (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 3 -->
let x = ... in (\d -> if d > 0 then d * (x (d-1)) else 1) 3
Oh, did you just see that? That x became a function inside our then branch.
let x = ... in if 3 > 0 then 3 * (x (3 - 1)) else 1) -->
let x = ... in 3 * x 2 -->
let x = ... in 3 * (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 2 -->
In the above you need to remember x = f x, hence the two arguments of x 2 at the end instead of just 2.
let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->
And I'll stop here!
How I understand it is, it finds a value for the function, such that it outputs the same thing you give it. The catch is, it will always choose undefined (or an infinite loop, in haskell, undefined and infinite loops are the same) or whatever has the most undefineds in it. For example, with id,
λ <*Main Data.Function>: id undefined
*** Exception: Prelude.undefined
As you can see, undefined is a fixed point, so fix will pick that. If you instead do (\x->1:x).
λ <*Main Data.Function>: undefined
*** Exception: Prelude.undefined
λ <*Main Data.Function>: (\x->1:x) undefined
[1*** Exception: Prelude.undefined
So fix can't pick undefined. To make it a bit more connected to infinite loops.
λ <*Main Data.Function>: let y=y in y
^CInterrupted.
λ <*Main Data.Function>: (\x->1:x) (let y=y in y)
[1^CInterrupted.
Again, a slight difference. So what is the fixed point? Let us try repeat 1.
λ <*Main Data.Function>: repeat 1
[1,1,1,1,1,1, and so on
λ <*Main Data.Function>: (\x->1:x) $ repeat 1
[1,1,1,1,1,1, and so on
It is the same! Since this is the only fixed point, fix must settle on it. Sorry fix, no infinite loops or undefined for you.
As others pointed out, fix somehow captures the essence of recursion. Other answers already do a great job at explaining how fix works. So let's take a look from another angle and see how fix can be derived by generalising, starting from a specific problem: we want to implement the factorial function.
Let's first define a non recursive factorial function. We will use it later to "bootstrap" our recursive implementation.
factorial n = product [1..n]
We approximate the factorial function by a sequence of functions: for each natural number n, factorial_n coincides with factorial up to and including n. Clearly factorial_n converges towards factorial for n going towards infinity.
factorial_0 n = if n == 0 then 1 else undefined
factorial_1 n = n * factorial_0(n - 1)
factorial_2 n = n * factorial_1(n - 1)
factorial_3 n = n * factorial_2(n - 1)
...
Instead of writing factorial_n out by hand for every n, we implement a factory function that creates these for us. We do this by factoring the commonalities out and abstracting over the calls to factorial_[n - 1] by making them a parameter to the factory function.
factorialMaker f n = if n == 0 then 1 else n * f(n - 1)
Using this factory, we can create the same converging sequence of functions as above. For each factorial_n we need to pass a function that calculates the factorials up to n - 1. We simply use the factorial_[n - 1] from the previous step.
factorial_0 = factorialMaker undefined
factorial_1 = factorialMaker factorial_0
factorial_2 = factorialMaker factorial_1
factorial_3 = factorialMaker factorial_2
...
If we pass our real factorial function instead, we materialize the limit of the series.
factorial_inf = factorialMaker factorial
But since that limit is the real factorial function we have factorial = factorial_inf and thus
factorial = factorialMaker factorial
Which means that factorial is a fixed-point of factorialMaker.
Finally we derive the function fix, which returns factorial given factorialMaker. We do this by abstracting over factorialMaker and make it an argument to fix. (i.e. f corresponds to factorialMaker and fix f to factorial):
fix f = f (fix f)
Now we find factorial by calculating the fixed-point of factorialMaker.
factorial = fix factorialMaker