7

I'd like to compose functions in a certain way. Please consider these 2 functions in pseudocode (not F#)

F1 = x + y
F2 = F1 * 10 // note I did not specify arguments for F1, 'reverse curry' for lack of a better word

What I would like for F# to do is figure out that since

let F1 x y = x + y
//val F1 : int -> int -> int

the code let F2 = F1 * 10 would give me the same signature as F1: val F2 : int -> int -> int, and calling F2 2 3 would result in 50: (2 + 3) * 10. That would be rather clever...

What happens is quite different tho. The first line goes as expected:

let F1 x y = x + y
//val F1 : int -> int -> int

but when I add a second line let F2 = F1 * 10 it throws off F#. It complains that the type int does not match the type 'a -> 'b -> 'c and that F1 now requires member ( + ).

I could of course spell it out like this:

let F1(x, y) = x + y
let F2(x, y) = F1(x, y) * 10

But now I might as well have used C#, we're not that far away anymore. The tupled arguments break a lot of the elegance of F#. Also my real functions F1 and F2 have a lot more arguments than just 2, so this makes me go cross eyed, exactly what I wanted to dodge by using F#. Saying it like this would be much more natural:

let F1 x y = x + y
let F2 = F1 * 10

Is there any way I can (almost) do that?

For extra credits: what exactly goes on with these error messages? Why does the second line let F2 = F1 * 10 change the typing on the first?

Thanks in advance for your thoughts,

Gert-Jan

update Two apporaches that (almost) do what's described.

One using a tuple. Second line looks a little quirky a first, works fine. Small drawback is I can't use currying now or I'll have to add even more quirky code.

let F1 (a, b) = a + b
let F2 = F1 >> (*) 10

F2(2, 3) // returns 50

Another approach is using a record. That is a little more straight forward and easier to get at first glance, but requieres more code and ceremony. Does remove some of the elegance of F#, looks more like C#.

type Arg (a, b) =
    member this.A = a
    member this.B = b

let F1 (a:Arg) = a.A + a.B
let F2 (a:Arg) = F1(a) * 10

F2 (Arg(2, 3)) // returns 50
gjvdkamp
  • 9,929
  • 3
  • 38
  • 46
  • 3
    Have a look at [this recent question](http://stackoverflow.com/questions/5821089/haskell-function-composition-operator-of-type-cd-abc-abd) on composition in Haskell. It's essentially the same problem, but there are some interesting solutions there that might be possible to implement in F# (I'm not a native speaker). – hammar May 04 '11 at 12:31
  • 1
    hi Hammar thanks, that Haskell makes me feel like i'm dyslexic.. I guess this is what they mean with 'condensed soup'. I'll give it a try later, maybe all of Haskell, does look like a good 'gym for the mind'. Thanks! – gjvdkamp May 04 '11 at 14:58
  • 2
    If `F1` took a tuple, then `F2` could be spelled out as `let F2 = F1 >> (*) 10` – ildjarn May 04 '11 at 18:26
  • @ildjarn thanks for the example, I didn't get the tupled approach at first. I updated the OP with that approach as well. – gjvdkamp May 05 '11 at 08:39

3 Answers3

5

There is no pattern for this in general. Using combinators (like curry and uncurry) as suggested by larsmans is one option, but I think the result is less readable and longer than the explicit version.

If you use this particular pattern often, you could define an operator for multiplying a function (with two parameters) by a scalar:

let ( ** ) f x = fun a b -> (f a b) * x

let F1 x y = x + y
let F2 = F1 ** 10

Unfortunately, you cannot add implementation of standard numeric operators (*, etc.) to existing types (such as 'a -> 'b -> int). However, this is quite frequent request (and it would be useful for other things). Alternatively, you could wrap the function into some object that provides overloaded numeric operators (and contains some Invoke method for running the function).

I think an appropriate name for this would be lifting - you're lifting the * operator (working on integers) to a version that works on functions returning integers. It is similar to lifting that is done in the C# compiler when you use * to work with nullable types.

To explain the error message - It complains about the expression F1 * 10:

error FS0001: The type 'int' does not match the type ''a -> 'b -> 'c'

I think it means that the compiler is trying to find an instantiation for the * operator. From the right-hand side, it figures out that this should be int, so it thinks that the left-hand side should also be int - but it is actually a function of two arguments - something like 'a -> 'b -> c'.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Hi, so I guess the short answer is no, you cannot have a HO function that can multiply any function with any scalar. Because F# is statically typed with generics under the hood, it does need to know the number of arguments for said function? Does that sum it up correctly? Then there is no way to leave the number of arguments open for now and the workaround becomes creating that HO function as an operator, that does the uncurry and multiplication in one go, but it would be specific for a function with 2 args? – gjvdkamp May 04 '11 at 14:37
  • 1
    Yes, that's correct. The only way to leave the number of parameters to a function open is to use some nasty reflection tricks (but that doesn't quite give you what you want). However, you could pass parameters around in a tuple (using single-parameter functions). – Tomas Petricek May 04 '11 at 15:47
  • Hmm.. guess i'd go for a record then, just so I can have the arguments named to avoid possibility of mixup. Actually that would a fine design and then Id only have to pass that one record down. Would be great if there was a language feature (as described) to support this but I guess a record would work just fine. – gjvdkamp May 05 '11 at 08:03
4

That would be rather clever...

So clever that it would beat the hell out of the type system. What you want is array programming as in APL.

Is there any way I can (almost) do that?

I don't speak F#, but in Haskell, you'd uncurry F1, then compose with *10, then curry:

f2 = curry ((*10) . uncurry f1)

Which in an ML dialect such as F# becomes something like:

let curry f x y = f (x,y)
let uncurry f (x,y) = f x y

let mult x y = x * y

let F1 x y = x + y
let F2 = curry (uncurry F1 >> mult 10)

(I wasn't sure if curry and uncurry are in the F# standard library, so I defined them. There may also be a prettier way of doing partial application of * without defining mult.)

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • 'clever' LOL I guess so.. Also often when I describe what I would really like to do APL or haskell tend to pop up. So many pretty languages, so little time.. Let me try to get my head around what you and Tomas said, and get back here. – gjvdkamp May 04 '11 at 13:54
  • I like the abstraction of this approach where the function is curried and uncurried, although these too are specific for 2 args. Haven't found any generic operator for that either in F#, Haskell can do that for any function? That's nice. The technique does however introduce quite some 'technical noise' where there's a lot of code that is about spelling out the 'how' instead of just the 'what'. Ideally I'd like to get code that reads just like you'd write it on a whiteboard for a non-prog but I guess you're right about that being hard on a parser/typesystem to work out for all different cases. – gjvdkamp May 04 '11 at 14:51
  • @gjvdkamp: some amount of typeclass magic might do the trick in Haskell. I don't know if F# supports typeclasses. – Fred Foo May 04 '11 at 16:07
  • I'd have to look into that. The major reason for me to switch to F# was to be able to write code more 'intuitively' without all the curly braces, return statements and mutable variables etc that C# has. So although F# and Haskell might provide tools to do what I descibed, it would introduce a lot of stuff that looks like 'magic spells'. That would defeat my main purpose of swiching to F#. Thanks for your time! – gjvdkamp May 05 '11 at 08:35
1

BTW, using point-free (or rather pointless in this case) approach one could define these functions in the following way:

let F1 = (+)
let F2 = (<<)((*)10) << F1
Ed'ka
  • 6,595
  • 29
  • 30