Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't the seq computation builder allow "let!"

Tags:

f#

I noticed that the following code gives an error when you try to compile it:

let xx =
  seq {
    let! i = [ 1; 2 ]
    let! j = [ 3; 4 ]
    yield (i,j)
  }

The error this gives is "error FS0795: The use of 'let! x = coll' in sequence expressions is no longer permitted. Use 'for x in coll' instead." This message is of course clear and demonstrates how to fix it; the fixed code would be:

let xx =
  seq {
    for i in [ 1; 2 ] do
      for j in [ 3; 4 ] do
        yield (i,j)
  }

My question is not how to fix this however, but why the "let!" is not allowed in sequence expressions in the first place? I can see how the fact that let! iterates over an expression may come as a surprise to some, but that shouldn't be enough to disallow the construct. I also see how "for" is more powerful here, as the version with "let!" bakes in the scope of the iteration as "until the end of the sequence expression".

However, being able to iterate over a sequence without having to indent code was exactly what I was looking for (for traversing tree structures). I assume that to obtain this semantic I will have to make a new expression builder that acts mostly like the "seq" expression builder, but does allow "let!" for iteration, isn't it?


Added, based on Brian's comment below, providing the solution to my underlying problem:

I didn't realize the indentation in the for blocks isn't needed, and the second sample can be re-written as:

let xx =
  seq {
    for i in [ 1; 2 ] do
    for j in [ 3; 4 ] do
    yield (i,j)
  }

... which gets rid of the ever-increasing indentation when traversing a tree structure. The syntax even allows additional statements in between for statements without requiring extra indentation, as in:

let yy =
  seq {
    for i in [ 1; 2 ] do
    let i42 = i+42
    for j in [ 3; 4 ] do
    yield (i42,j)
  }

Now, if only I could figure out why I thought these statements would require indentation...

like image 537
Luc C Avatar asked Apr 18 '12 08:04

Luc C


People also ask

What is SEQ in F#?

Seq. groupBy takes a sequence and a function that generates a key from an element. The function is executed on each element of the sequence. Seq. groupBy returns a sequence of tuples, where the first element of each tuple is the key and the second is a sequence of elements that produce that key.

What is yield in F#?

F# Sequence Workflows yield and yield! (pronounced yield bang) inserts all the items of another sequence into this sequence being built. Or, in other words, it appends a sequence. (In relation to monads, it is bind .)

What is computation expression?

Computation expressions in F# provide a convenient syntax for writing computations that can be sequenced and combined using control flow constructs and bindings. Depending on the kind of computation expression, they can be thought of as a way to express monads, monoids, monad transformers, and applicative functors.

What is the underlying difference between a sequence and a list in F #?

The list is created on declaration, but elements in the sequence are created as they are needed. As a result, sequences are able to represent a data structure with an arbitrary number of elements: > seq { 1I ..


1 Answers

Just a few weeks ago, I wrote a paper with Don Syme that tries to explain some of the motivation behind the syntax choices in F# computation expressions (such as sequence expressions, asynchronous workflows and others). You can find it here. It does not give definite answer to your question, but it may help.

In general, when you have some type M<'T> and you're defining computation builder, you can add methods For and Bind to enable for and let! syntax:

For  : seq<'T> -> ('T -> M<'T>) -> M<'T>
Bind : M<'T>   -> ('T -> M<'T>) -> M<'T>

The input for For should always be some sequence seq<'T>, while the input for Bind should be the type M<'T> for which you're defining it.

In sequence expressions, these two operations would have the same type and so they would have to do the same thing. Although sequence expressions could provide both, it is probably a good idea to allow just one, because using two different keywords for one thing would be confusing. A general principle is that code in comp { .. } block should behave like normal code - and since for exists in normal F# code, it makes sense to use the same syntax inside computation expression for seq<'T>.

like image 61
Tomas Petricek Avatar answered Nov 13 '22 06:11

Tomas Petricek