Оператор Haskell to-infix в F #

Существует обычная проблема: F # не поддерживает в стиле infix-стиле функции, доступные в Haskell:

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

Наиболее известное решение для F # можно найти здесь:

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

Кроме того, его легко немного улучшить, используя приоритет собственных операторов:

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

Также существует XML-ish </style/>, описанный здесь.

Я хотел бы найти лучшее решение со следующими критериями:

  • Оператор одиночного символа (или пара), который не уничтожает обычно используемые операторы;
  • В Haskell должен присутствовать один и тот же символ, так же как и символ серьезного акцента (обратной цитаты);
  • Он не должен уничтожать ассоциативность (цепочка поддержки):

    let found = "barZZZ" |>truncateAt<| 3 |>isInfixOf<| "foobarbaz"
    
  • Необязательно, он должен поддерживать функции, берущие кортежи:

    let isInfixOf (what:string, where:string) = ...
    // it will not work with |> and <|
    
  • Необязательно, он должен изящно обрабатывать функции /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. Различные трюки для кодирования также приветствуются, поскольку я считаю, что хороший (когда проверяется командой F # Dev) может быть частью языка в будущем.

Ответ 1

Я согласен с тем, что способность превращать функции в операторы infix в Haskell в некоторых ситуациях является опрятной. Тем не менее, я не уверен, что эта функция хорошо впишется в обычный стиль программирования F #, так как это может быть достигнуто с помощью членов.

Например, давайте возьмем ваш фрагмент, который использует truncateAt и isInfixOf:

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

Если мы определяем truncateAt и isInfixOf как методы расширения string, вы можете написать:

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

Эта версия короче, и я лично считаю, что она также более читаема (особенно для тех, кто использует фон программирования .NET в отличие от фона Haskell). Вы также получаете IntelliSense, когда вы нажимаете ., что является хорошим бонусом. Конечно, вы должны определить эти операции как методы расширения, поэтому вам нужно более тщательно рассмотреть дизайн ваших библиотек.

Для полноты методы расширения определяются следующим образом:

type System.String with
  member what.IsInfixOf(where:string) = 
    where.IndexOf(what, StringComparison.OrdinalIgnoreCase) >= 0 
  member x.TruncateAt(n) = 
    x.Substring(0, n)

Ответ 2

let (|<) f x = f x
let f = sprintf "%s%s"
let x = "A" |> f |< "B" |> f |< "C" //"ABC"