Hi! It's just today that I tried to do the same thing with factorials (as I'm learning F# too). However I'm seeing that your code can be made more elegant.

Firstly, there is a sweet shortcut notation for generating number range lists like yours. Instead of writing

1
[for i in 1 .. n do yield i]

you could use this:

[1 .. n]

Secondly, it's a list that you generate but you use generic [b]Seq.fold[/b] that works for any sequence. I'd rather use [b]List.fold[/b] in this case because it makes the use of lists more explicit and is [i]probably[/i] faster with lists—at least it well may be. I see no reason for abstraction here.

But wait. [b]Fold[/b] is nice when we have some special 'state' type that we want to accumulate while folding the list. Here we can use [b]List.reduce[/b] instead, which is less generic than [b]List.fold[/b] but is simpler. It only works when the first list item can be taken as initial value and the type of accumulator matches the type of list, but this is precisely our case.

Seeing that you started this thread, I guess you enjoy using functional style where possible. You should not forget then that [b]*[/b] operator is itself a function of type [b](int -> int -> int)[/b] so you can avoid writing redundant lambda and instead pass [b](*)[/b] as a function value.

To sum up, this is how I'd put it:

1
let fact n = [1 .. n] |> List.reduce (*)

Best regards,

Daniel

By on 6/21/2010 3:27 PM ()

Interesting observations Daniel. You're right; you're implementation is more compact for sure.

An even neater feature of the list comprehension

1
2
[1 .. n]

is the fact that it also works with arrays and sequences:

1
2
3
4
[|1 .. n |]

{1 .. n}

The more I work with F# the more I wish I could do it for my day job. I'm just looking forward to the day when the rest of the world catches up with the smarts that inform F#.

But my original point was the effective equivalence between the recursive version of the code and the Seq.fold version. I'm not sure that catamorphisms would also apply in the case of a List.reduce but I would be interested to find out.

I've been trying to figure out how to do a Seq.fold version of the tail-call optimized version of those functions which use continuations.

By on 6/21/2010 7:14 PM ()

You sound like you're ready to enjoy my 8-part series on catamorphisms. :)

Part one: [link:lorgonblog.spaces.live.com]

answers your question kind of. (It's a little different, since you start with factorial n, which just takes a number, n, rather than the list of numbers 1..n to multiply, but similar idea.)

(My rough approximation of one headline summary would be "you never need to write a recursive function, you can always use a fold instead".)

By on 5/14/2010 2:13 PM ()

To be pedantic, I'd restrict Brian's claim to structural recursion. For instance, take a discriminated union representing natural numbers:

1
type Nat = Z | S of Nat

Here Z represents zero and S constructs the successor to another natural number. Then the fold operator for this type is:

1
2
3
let rec fold z s = function
| Z -> z
| S n -> s (fold z s n)

Now we can define any function which could have been defined by recursion and case analysis using the fold instead:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let add n = fold n S


let mul n = fold Z (add n)


let fact = fold (S Z, Z) (fun (N, n) -> mul N (S n), S n) >> fst


let toInt = fold 0 ((+) 1)


let one = S Z
let two = S one
let three = S two
let six = fact three
let sixI = six |> toInt

Note: because we're using a unary representation here, don't call fact on anything bigger than four or five, or the resulting value will be a bit annoying to view...

You can also do the same thing with actual ints instead of our discriminated union above, which may be easier to read:

1
2
3
4
5
6
7
8
9
10
11
module Int = begin
  
  let rec fold z s = function
  | 0 -> z
  | n -> s (fold z s (n-1))
  
  let add n = fold n ((+) 1)
  let mul n = fold 0 (add n)
  let fact = fold (1,0) (fun (N,n) -> N*(n+1), n+1) >> fst

end

The key to being able to do this is that we're still conceptually using a structural basis on which we want to

recurse, which allows us to define the fold in a natural way. However, for problems which don't break down nicely using this recursion structure, we will have a tough time using this fold to represent the recursion. For instance, take the following fast exponentiation definition:

1
2
3
4
5
6
7
8
let rec powi n = function
| 0 -> 1
| e ->
    let n' = powi n (e/2)
    if (e%2 = 1) then 
      n' * n' * n
    else
      n' * n'

Even though this is a function defined using recursion on an integral argument, there's no good way to represent this function with our fold because for an argument e we're recursing on (e/2), not (e-1).

By on 5/14/2010 3:17 PM ()

Thanks Brian and KVB. I do appreciate you explaining my little epiphany in a bit more concrete terms. I need to ponder the points you guys have raised to me. Seems more like Lambda the Ultimate type discussion and I'm afraid my math background is somewhat lacking. But I do appreciate both of you trying to explain this in slightly less "lambda calculus" terms.

I see also from Brian's discussion that there was an approach I'd not considered that removed that second parameter to factorial. That also sort of explains to me why it was so annoying to have a second parameter to a factorial function--because it breaks the symmetry with the mathematical function. You don't specify a factorial in math with two arguments--you specify with only the number you want to calculate the factorial of.

I'm pleased that I seem to have made progress in my understanding of FP on my own--maybe I'm finally starting to grok this stuff.

Hey, Brian have you written those additional blog posts on catamorphisms yet? I didn't see a link for the other parts of your discussion of catamorphisms.

By on 5/18/2010 5:11 AM ()

Yeah, there are 8 parts, click the 'summary' link on the left of my blog, and then poke around the navigation to find all the others. I should really link them all to each other, like a good blogger, but I am a slacker.

By on 5/18/2010 9:49 AM ()

Hi Brian,

Just a quick question regarding the second part of the catamorphics discussion.

When I add the closures for InOrder and Height:

1
2
let InOrder = FoldTree (fun x l r -> l @ [x] @ r) [] 
let Height  = FoldTree (fun _ l r -> 1 + max l r) 0

I get an FS0030 error (under F# 2.0)--well here's the error message for InOrder:

1
2
3
4
stdin(33,5): error FS0030: Value restriction. The value 'InOrder' has been inferred to have generic type
    val InOrder : (Tree<'_a> -> '_a list)
Either make the arguments to 'InOrder' explicit or, if you do not intend for it
to be generic, add a type annotation.

If I modify the definition to this:

1
2
let InOrder = FoldTree (fun (x:int) l r -> l @ [x] @ r) [] 
let Height  = FoldTree (fun (x:int) l r -> 1 + max l r) 0

Then everything works fine. So is there some way to make this generic and have it work?

I tried

1
let InOrder = FoldTree (fun (x:Node) (l:Tree<'a>) (r:Tree<'a>) -> l @ [x] @ r) [];;

But that doesn't work.

By on 5/18/2010 8:34 PM ()

Read the comments at the end of the blog entry.

By on 5/18/2010 11:16 PM ()

Thanks.

My bad--I had seen those comments and when I was working on the code last night I was trying to reproduce the code from memory and I didn't recall the comments.

By on 5/19/2010 4:11 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper