Ну, как уже отмечалось в комментариях, написание императивного кода в стиле без точек вообще не очень хорошая идея.Он не только не делает его более читабельным , но и делает его более подверженным ошибкам , поскольку его выполнение более сложно рассуждать.Даже с функциональным кодом я часто нахожу, что стиль без точек гораздо более читабелен (и не намного дольше).
В любом случае, как вы и просили, вот несколько шагов, которые вы можете предпринять - обратите внимание, что этодействительно упражнение в функциональной запутанности.Не то, что вы бы хотели сделать.
Код для первой функции должен быть похож на этот (это будет менее эффективно , потому что выражения последовательности оптимизированы):
let rec toSeq (reader : SqlDataReader) toItem = Seq.delay (fun () ->
if reader.Read() then
Seq.concat [ Seq.singleton (toItem reader); toSeq reader toItem ]
else
Seq.empty)
Evenв стиле без точек вам все еще нужен Seq.delay
, чтобы убедиться, что вы выполняете последовательность лениво.Однако вы можете определить немного другую функцию, которая позволит вам писать код в более бессмысленном стиле.
// Returns a delayed sequence generated by passing inputs to 'f'
let delayArgs f args = Seq.delay (fun () -> f args)
let rec toSeq2 : (SqlDataReader * (SqlDataReader -> int)) -> seq<int> =
delayArgs (fun (reader, toItem) ->
if reader.Read() then
Seq.concat [ Seq.singleton (toItem reader); toSeq2 (reader, toItem) ]
else
Seq.empty)
Теперь тело функции - это просто некоторая функция, переданная функции delayArgs
.Мы можем попробовать составить эту функцию из других функций в стиле без точек.Хотя if
сложно, поэтому мы заменим его комбинатором, который принимает три функции (и передает всем один и тот же вход):
let cond c t f inp = if c inp then t inp else f inp
let rec toSeq3 : (SqlDataReader * (SqlDataReader -> int)) -> seq<int> =
delayArgs (cond (fun (reader, _) -> reader.Read())
(fun (reader, toItem) ->
Seq.concat [ Seq.singleton (toItem reader);
toSeq3 (reader, toItem) ])
(fun _ -> Seq.empty))
Вы не можете обрабатывать вызовы членов в бессмысленной точке.стиль, поэтому вам нужно определить функцию, которая вызывает Read
.Затем вы также можете использовать функцию, которая возвращает постоянную функцию (чтобы избежать коллизий имен, с именем konst
):
let read (reader:SqlDataReader) = reader.Read()
let konst v _ = v
Используя два, вы можете превратить последний и второй аргумент в бессмысленныйстиль:
let rec toSeq4 : (SqlDataReader * (SqlDataReader -> int)) -> seq<int> =
delayArgs (cond (fst >> read)
(fun (reader, toItem) ->
Seq.concat [ Seq.singleton (toItem reader);
toSeq4 (reader, toItem) ])
(konst Seq.empty))
Использование более сумасшедших комбинаторов (uncurry
Существуют в Haskell; комбинатор list2
также может быть написан в стиле без очков, но я думаю, вы поняли идею):
let list2 f g inp = List.Cons(f inp, List.Cons(g inp, []))
let uncurry f (a, b) = f a b
let rec toSeq5 : (SqlDataReader * (SqlDataReader -> int)) -> seq<int> =
delayArgs (cond (fst >> read)
(list2 ((uncurry (|>)) >> Seq.singleton) toSeq5 >> Seq.concat)
(konst Seq.empty))
Это не совсем компилируется, потому что toSeq5
оценивается как часть его определения, но если вы включили некоторую функцию задержки, она может фактически сделать то же самое, что и изначально.
Резюме - Я больше не знаю, является ли приведенный выше код правильным и как он оценивает (это может с нетерпением оценить читателя или содержать какую-то другую ошибку).Он выполняет проверку типов, так что, вероятно, он не слишком далек от работы.Код полностью нечитаем, трудно отлаживать и невозможно изменить.
Думайте об этом как о крайнем примере сумасшедшего кода без точек, который вы можете написать на F #.На практике я думаю, что стиль без точек должен использоваться только для таких тривиальных вещей, как составление функций с использованием >>
.Если вам нужно определить комбинаторы, такие как uncurry
или konst
, людям будет действительно трудно читать ваш код.