写f#序列表达点免费

时间:2012-02-21 22:24:12

标签: f# pointfree

我正在学习写点免费,事情变得很好,直到遇到这个:

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

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

我需要确保使用仍然正常。

仅供参考:我知道在这里使用免费点编程似乎毫无意义。请理解这对我来说是一次学习练习。

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

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

1 个答案:

答案 0 :(得分: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))

使用一些更疯狂的组合器(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#编写的疯狂无点代码的极端示例。在实践中,我认为无点样式应仅用于使用>>组合函数等微不足道的事情。一旦您需要定义uncurrykonst等组合器,人们就很难阅读您的代码。