5

There is a common problem that F# does not natively support infix-style use of functions that is available in Haskell:

isInfixOf :: Eq a => [a] -> [a] -> Bool
isInfixOf "bar" "foobarbaz"
"bar" `isInfixOf` "foobarbaz"

The best known solution for F# can be found here:

let isInfixOf (what:string) (where:string) =
    where.IndexOf(what, StringComparison.OrdinalIgnoreCase) >= 0
let found = "bar" |>isInfixOf<| "foobarbaz"

Also, it is easy to improve it a bit, employing native operators precedence:

let ($) = (|>)
let (&) = (<|)
let found = "bar" $isInfixOf& "foobarbaz"

There's also XML-ish </style/>, described here.

I would like to find a better solution, with the following criteria:

  • Single character operator (or a pair) that does not destroy commonly used operators;
  • It should be the same character, likewise grave accent (back quote) character serves in Haskell;
  • It should not destroy associativity (support chaining):

    let found = "barZZZ" |>truncateAt<| 3 |>isInfixOf<| "foobarbaz"
    
  • Optionally, it should support functions taking tuples:

    let isInfixOf (what:string, where:string) = ...
    // it will not work with |> and <|
    
  • Optionally, it should gracefully handle functions/3:

    val f: 'a -> 'b -> 'c -> 'd = ...
    let curried = a |>f<| b c
    // this wouldn't compile as the compiler would attempt to apply b(c) first
    

P.S. Various coding tricks are also welcome as I believe the good one (when checked by the F# Dev team) can be a part of the language in the future.

Community
  • 1
  • 1
Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66

2 Answers2

17

I agree that the ability to turn functions into infix operators in Haskell is neat in some situations. However, I'm not sure if this feature would fit well with the usual F# programming style, because the same can be achieved using members.

For example, let's take your snippet that uses truncateAt and isInfixOf:

let found = "barZZZ" |>truncateAt<| 3 |>isInfixOf<| "foobarbaz" 

If we define TruncateAt and IsInfixOf as extension methods of string, then you can write:

let found = "barrZZZ".TruncateAt(3).IsInfixOf("foobarbaz") 

This version is shorter and I personally think it is also more readable (espcially to someone with .NET programming background as opposed to Haskell background). You also get IntelliSense when you hit ., which is a nice bonus. Of course, you have to define these operations as extension methods, so you need to more carefuly consider the design of your libraries.

For completeness, the extension methods are defined as follows:

type System.String with
  member what.IsInfixOf(where:string) = 
    where.IndexOf(what, StringComparison.OrdinalIgnoreCase) >= 0 
  member x.TruncateAt(n) = 
    x.Substring(0, n)
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • 2
    Well, there are some operators that are considered idiomatic - like `|>` (and possibly also `<|`, although I do not use this one often). I'm happy with these. Other languages have other idiomatic constructs... sometimes these map directly to F#, sometimes they do not. I think the Haskell infix function notation does not. – Tomas Petricek Sep 19 '12 at 17:17
  • 1
    @7sharp9 `if xs ⊆ ys then ...` – Be Brave Be Like Ukraine Sep 19 '12 at 17:17
  • 6
    @Tomas: While I agree with your premise that infix calls don't fit the F# style, methods are a very poor substitute; you forego all the benefits of functions. – Daniel Sep 19 '12 at 17:18
  • 3
    @7sharp9: The key to names _and_ symbols is familiarity. `adsfkj` is no better than `~!@>`. – Daniel Sep 19 '12 at 17:20
  • Relevant: http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html – Mauricio Scheffer Sep 19 '12 at 17:26
  • The inbuilt symbolic will always have familiarity, I have used some custom symbols myself, use with care, the lack of intellisense makes them really painfull. – 7sharp9 Sep 19 '12 at 17:32
  • 1
    @Daniel I think that, in this case, extension methods provide a perfectly sufficient substitute. I'm not saying this is the case in general - but if I was designing F#, I would probably try to find a way to make methods and functions more equal than trying to add, say, infix function syntax. One way to do that would be to allow syntax like `#IsInfixOf` for first-class methods (i.e. `#TruncateAt 3 >> #IsInfixOf`). Adding this to F# is, sadly, not as easy as it might look. – Tomas Petricek Sep 19 '12 at 18:43
  • @bytebuster Unicode operators are cool. The only thing that worries me is: http://en.wikipedia.org/wiki/File:APL-keybd2.svg – Tomas Petricek Sep 19 '12 at 18:47
  • 2
    @TomasPetricek: I agree; first-class methods are even better. – Daniel Sep 19 '12 at 18:50
  • 1
    All this stuff sounds cool when its your own library, when you unleash it on the rest of the world its a different matter. I guess thats why the F# code guidelines try to steer you away from that. – 7sharp9 Sep 19 '12 at 22:48
  • 3
    @7sharp9 I for one can understand operators better and I want our options to be not limited for the sake of newcomers. Can you suggest a name for `(.>>.)`, `(.>>)`, `(>>.)` from `FParsec`? Assuming you are not a new comer yourself and not giving any links. – Cetin Sert Sep 20 '12 at 12:40
  • 2
    Sorry, I didn't notice the comments until someone reposted a link here. They all have real names and descriptions, .>>.is tupleTwo, .>> returnLeft etc etc, the point I was making is you have to look up names and meaning for symbolics when they are not clear. IntelliSense also doesn't work on symbols, but it does on non-symbolics. – 7sharp9 Dec 12 '12 at 00:47
2
let (|<) f x = f x
let f = sprintf "%s%s"
let x = "A" |> f |< "B" |> f |< "C" //"ABC"
Daniel
  • 47,404
  • 11
  • 101
  • 179