Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell style to-infix operator in F#

Tags:

f#

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.

like image 472
bytebuster Avatar asked Sep 19 '12 15:09

bytebuster


1 Answers

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)
like image 70
Tomas Petricek Avatar answered Sep 23 '22 10:09

Tomas Petricek