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 も正しく機能することを確認する必要があります。

参考までに: ここでポイントフリー プログラミングを使用するのは無意味に思えるかもしれません。これは私にとって学習演習であることを理解してください。

更新: これは、2 番目の関数に対する私の最初の試みです。ただし、シーケンス参照を削除する必要がありました。

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はトリッキーなので、3 つの関数を受け取る (そしてそれらすべてに同じ入力を渡す) コンビネータに置き換えます。

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。次に、定数関数を返す関数を使用することもできます (名前の衝突を避けるため、named konst):

let read (reader:SqlDataReader) = reader.Read()
let konst v _ = v

この 2 つを使用すると、最後の引数と 2 番目の引数をポイントフリー スタイルに変えることができます。

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が、遅延関数を挿入すると、実際には元の動作と同じ動作をする可能性があります。

要約- 上記のコードが正しいかどうか、またそれがどのように評価されるかはもうわかりません (リーダーを熱心に評価するか、他の種類のバグが含まれている可能性があります)。型チェックを行うので、おそらく動作にはそれほど遠くありません。コードは完全に判読できず、デバッグも修正も不可能です。

これは、F# で記述できるクレイジーなポイントフリー コードの極端な例と考えてください。実際には、ポイントフリー スタイルは、 を使用して関数を作成するなどの些細なことにのみ使用する必要があると思います>>uncurryまたはのようなコンビネータを定義する必要があるとkonst、人々がコードを読むのは非常に難しくなります。

于 2012-02-22T12:39:02.400 に答える