Tuesday, June 15, 2010

Flip Operator for F#?

I posted the following message on hubFS:

“Situations often arise where I need to curry the second argument of a function. For example, the divisor in (/). It’s not hard to come up with a function or operator for doing this, as the following code illustrates. But is there a way to do it in F# without defining a new function or operator?”

For instance, one might want to curry the second argument of the divide function (/) in order to produce a curried “divide-by-N” function. This kind of operation is very common in stack-based languages like FORTH, but is not usually needed in imperative languages.

Here is some code that illustrates what I want to do. I decided to use “><” to represent the “flip*” or “swap” operator.

// This code is presented "as-is" and without warranty 
// or implied fitness of any kind; use at your own risk. 
 
// "Flip" or "swap" operations:
 
let flip f a b = f b a
let (><) f a b = f b a
 
// Sundry tests:
 
let thisIs20 = ((/) >< 5) 100
let also20 = ((><) (/) 5) 100
 
let d5'0 = flip (/) 5
let d5'1 = (/) >< 5
 
let m5'0 = flip (%) 5
let m5'1 = (%) >< 5
 
let t0 = d5'0 99 // 19
let t1 = d5'1 99 // 19
let t2 = m5'0 99 // 4
let t3 = m5'1 99 // 4
 
let divBy = (><) (/)
let divBy5 = divBy 5
let shouldBe20 = divBy5 100
 
// etc.


I ask again: is there a good native way to do this in F#? If not, it might well worth standardizing this. (And potentially other FORTH-like operators like “over” and “rot,” perhaps with an extensible syntax.)

-Neil

*Name suggested by “mau” on hubFS.

4 comments:

AshleyF said...

True, 'flip' is standard in the Haskell prelude. Strange that it's missing in F#. That and 'curry'/'uncurry'... Useful for point-free style, which I personally love

TechNeilogy said...

Thanks for the comment, Ashley. To steal a phrase from the Lord of the Rings: "Even the smallest operator can change the course of the future."

By the way, as someone who was first introduced to functional programming via LISP/Scheme, I really appreciate your FScheme series!

Chris Ballard said...

This is neat, one of my annoyances is when trying to simplify (for example) filter operations, eg:
[1..100] |> List.filter ((>)50)

Which counterintuitively returns all values which are less than 50, and the 50 applies to the other side of (>). So if I do:
[1.100] |> List.filter ((flip(>))50)

I get the intuitive result of all values greater than 50. Nice - thanks Neil.

TechNeilogy said...

Thank you, Chris! One of my favorite things about F# is the way even small changes to operators (etc.) can make a real improvement in the readability of code. That is, they can make the code "look like what it does." I think it's maintenance factors like this that are helping to drive the acceptance of F#.