Search code examples
f#lazy-evaluationcallbyname

Does F# support 'call-by-name' semantics?


For a while F# has supported the ability to auto-quote using [<ReflectedDefinitionAttribute>]. Is there anything similar for laziness?

e.g.

    member __.Quoted ([<ReflectedDefinitionAttribute>] quotation:Expr<'T>) = ... 
    member __.Thunked ([<LazyAttribute>] thunk:Lazy<'T>) = ... 

I suppose I could use something like

    member __.Quoted ([<ReflectedDefinitionAttribute>] quotation:Expr<'T>) = 
        Lazy (evaluate (<@ fun () -> %quotation @>)) // evaluate using Unquote or similar

But wouldn't this be costly?

UPDATE:

I found a hack, it's not exactly what I would like but it give the correct behavior.

type Signal = Signal with
    member __.Return x = x
    member __.Delay (f:unit -> _) = f

let a = Signal { return randint }
let b = Signal { return randint }
let c = Signal { return a() + b() }

Solution

  • There is nothing like the ReflectedDefinition attribute for automatically turning things into delayed Lazy<'T> computations.

    You are right that automatically quoting the argument achieves something like this. You could use the (very limited) LeafExpressionConverter.EvaluateQuotation to do this for some limited kinds of expressions, but as you note, this would be inefficient. The following is a proof of concept though (but you cannot call custom functions in the branches as this uses LINQ expressions):

    open Microsoft.FSharp.Quotations
    open Microsoft.FSharp.Linq.RuntimeHelpers
    
    type A = 
      static member If<'T>(c:bool,
          [<ReflectedDefinition>] t:Expr<'T>,
          [<ReflectedDefinition>] f:Expr<'T>) = 
        if c then LeafExpressionConverter.EvaluateQuotation t :?> 'T
        else LeafExpressionConverter.EvaluateQuotation f :?> 'T
    
    A.If(1 = 2, 0, 1)
    

    In practice, I think a more reasonable approach is to just use the built-in Lazy<'T> values. F# has a (not widely known) lazy keyword that gives you a bit nicer syntax for creating those:

    let iff c (t:Lazy<_>) (f:Lazy<_>) = 
      if c then t.Value else f.Value
    
    iff (1 = 2) 
      (lazy (printfn "true"; 41)) 
      (lazy (printfn "false"; 42))