0

我正在学习免费写作,事情进展顺利,直到我遇到这个:

let rec toSeq (reader : SqlDataReader) toItem = seq {
        if reader.Read()
        then
            yield toItem reader
            yield! toSeq reader toItem
        else
            yield! Seq.empty }

还有这个:

let execute sql init toItem = 
    seq {
        use command = new SqlCommand(sql)
        command |> init
        use connection = new SqlConnection("")
        connection.Open()
        command.Connection <- connection
        use reader = command.ExecuteReader()
        yield! toSeq reader toItem } |> Seq.toList

我不知道如何通过序列构建器......这甚至可能吗?

我需要确保 usings 仍然可以正常工作。

仅供参考:我知道在这里使用无点编程似乎毫无意义。只要明白这对我来说是一个学习练习。

更新:这是我对第二个功能的第一次尝试。我不得不删除序列引用:

let ExecuteReader (command : SqlCommand) (connection : SqlConnection) = 
    command.Connection <- connection
    command.ExecuteReader()

let c x y =  ((>>) x) << ((<<) << y)

let (>>|) = c

let execute =
    ExecuteReader 
    >>| ((>>) toSeq) (flip using) 
    >>| using 
    >>| using
4

1 回答 1

2

好吧,正如评论中已经指出的那样,以无点风格编写命令式代码根本不是一个好主意。它不仅不会使它更具可读性,而且会使其更容易出错,因为它更难以推理执行。即使使用函数式代码,我也经常发现非无点样式更具可读性(而且不会更长)。

无论如何,既然你问了,这里有一些你可以采取的步骤 - 请注意,这实际上是一个功能混淆练习。不是你想做的事。

第一个函数的代码将被简化为类似这样的代码(这会降低效率,因为序列表达式已经过优化):

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)

即使在无点样式中,您仍然需要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))

使用一些更疯狂的组合器(uncurryHaskell 中的存在;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作为其定义的一部分进行评估,但是如果您插入一些延迟函数,它实际上可能会做与最初所做的相同的事情。

总结- 我不知道上面的代码是否正确以及它如何评估(它可能会急切地评估读者,或者包含其他类型的错误)。它会进行类型检查,因此它可能离工作不远。代码完全不可读,难以调试且无法修改。

Think of this as an extreme example of crazy point-free code you can write in F#. In practice, I think that point-free style should only be used for trivial things like composing functions using >>. Once you need to define combinators like uncurry or konst, it will be really hard for people to read your code.

于 2012-02-22T12:39:02.400 回答