1

I want to write the following code:

let someAsync () = async {
    if 1 > 2 then return true // Error "this expression is expected to have type unit ..."
    // I want to place much code here    
    return false
}

F# for some reason thinks that I need to write it like that:

let someAsync () = async {
    if 1 > 2 then return true
    else
        // Much code here (indented!)
        return false
}

In latter case no error message is produced. But in my view both pieces of code are equivalent. Is there any chance I could avoid unnecessary nesting and indentation?

UPD. What I am asking is possible indeed! Please take a look at example, see section Real world example

I will quote the code:

let validateName(arg:string) = imperative {
    if (arg = null) then return false  // <- HERE IT IS
    let idx = arg.IndexOf(" ")
    if (idx = -1) then return false    // <- HERE IT IS

    // ......
    return true 
}

So, it is possible, the only question is if it is possible to implement somehow in async, via an extension to module or whatever.

Rustam
  • 1,766
  • 18
  • 34

2 Answers2

3

I think that situation is described here: Conditional Expressions: if... then...else (F#)

(...) if the type of the then branch is any type other than unit, there must be an else branch with the same return type.

Your first code does not have else branch, which caused an error.

MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
  • Interesting... I was sure I saw how such constructs `if xxx then return false` were used in custom computation expressions and I just wanted to check if it could work in `async` also. I will double check... – Rustam Apr 07 '14 at 08:02
  • See section Real-world example – Rustam Apr 07 '14 at 08:16
  • @Rustam - This is one of those things that you can do, but is probably a bad idea unless you really know what you are doing – John Palmer Apr 07 '14 at 09:36
  • @JohnPalmer Well, I find computation expression a very powerful tool, but a bit contrived indeed. Despite that I would rather try to get used to them – Rustam Apr 07 '14 at 10:13
  • 1
    Well, this quote only refers to ordinary `if` .. `then` .. `else` expressions not inside the computation expression block. Inside computation expressions, the situation is more subtle and it depends on how `Zero` and `Combine` members are defined. For more details see: http://tomasp.net/academic/papers/computation-zoo/ – Tomas Petricek Apr 07 '14 at 14:30
2

There is an important difference between the async computation builder and my imperative builder.

In async, you cannot create a useful computation that does not return a value. This means that Async<'T> represents a computation that will eventually produce a value of type 'T. In this case, the async.Zero method has to return unit and has a signature:

async.Zero : unit -> Async<unit>

For imperiatve builder, the type Imperative<'T> represents a computation that may or may not return a value. If you look at the type declaration, it looks as follows:

type Imperative<'T> = unit -> option<'T>

This means that the Zero operation (which is used when you write if without else) can be computation of any type. So, imperative.Zero method returns a computation of any type:

imperative.Zero : unit -> Imperative<'T>

This is a fundamental difference which also explains why you can create if without else branch (because the Zero method can create computation of any type). This is not possible for async, because Zero can only create unit-returning values.

So the two computations have different structures. In particular, "imperative" computations have monoidal structure and async workflows do not. In more details, you can find the explanation in our F# Computation Zoo paper

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • That's what I supposed. But I had a hope there might be some shortcut that would help. Nonetheless, thanks to your `imperative` example I created my own workflow which combines it with `async`. Might be not very smooth, but it works fine. – Rustam Apr 07 '14 at 17:22
  • @Rustam - I'd be curious about that - did you define your own type of computations? I'd think that it is not possible to define the second kind of `Zero` method for standard asyncs... – Tomas Petricek Apr 07 '14 at 22:48
  • Yes, I used your example and I wrapped `async` into it also, as a secondary kind of computation. Here it is: https://gist.github.com/GusRustam/10081383 I am not sure if that code is completely safe, but currently it works for me. – Rustam Apr 08 '14 at 01:44