Question

I am learning about writing point free and things were going great until I ran into this:

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

and this:

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

I have no idea how to get past the sequence builder... Is this even possible?

I need to make sure the usings still work correctly as well.

FYI: I know it might seem pointless to use point free programming here. Just understand this is a learning exercise for me.

UPDATE: Here is my first attempt on the second function. I had to remove the sequence references though:

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
Was it helpful?

Solution

Well, as already noted in the comments, writing imperative code in a point-free style is not a good idea at all. Not only it does not make it more readable, but it makes it more error-prone, because it is more difficult to reason about the execution. Even with functional code, I often find the non-point-free style a lot more readable (and not much longer).

Anyway, since you asked, here are some steps you can take - note that this is really an exercise in functional obfuscation. Not something you would ever want to do.

The code for the first function would be desugared to something like this (this is going to be less efficient because sequence expressions are optimized):

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 in the point-free style, you still need the Seq.delay to make sure that you're executing the sequence lazily. However, you can define slightly different function that lets you write code in a more point-free style.

// 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)

Now, the body of the function is just some function passed to the delayArgs function. We can try composing this function from other functions in a point-free style. The if is tricky though, so we replace it with a combinator that takes three functions (and passes the same input to all of them):

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))

You cannot treat member invocations in a point-free style, so you need to define function that calls Read. Then you can also use a function that returns constant function (to avoid name collisions, named konst):

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

Using the two, you can turn the last and the second argument to a point-free style:

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))

Using some more crazy combinators (uncurry existis in Haskell; the list2 combinator could be also written in a point-free style, but I think you get the idea):

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))

This does not quite compile, because toSeq5 is evaluated as part of its definition, but if you insered some delay function, it might actually do the same thing as what it did originally.

Summary - I don't know anymore if the code above is correct and how it evaluates (it might evaluate the reader eagerly, or contain some other kind of bug). It does type-check, so it probably isn't too far from working. The code is completely unreadable, hard to debug and impossible to modify.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top