New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splice types into Quotations #670

Open
TobyShaw opened this Issue May 17, 2018 · 6 comments

Comments

Projects
None yet
4 participants
@TobyShaw

TobyShaw commented May 17, 2018

Enable splicing types into Quotations

Currently, quotations have difficulty when dealing creating expressions where types are not known at compile time.

In the following example, we desire a function which gives us an expression corresponding to creating an array with type t with length n. In order to do this, we must use the generic function: Array.zeroCreate. This is a simple implementation of the desired function:

let replaceTypeOnCall (t : System.Type) = function
    | Call(o,mi,args) ->
        let newMI = mi.GetGenericMethodDefinition().MakeGenericMethod([|t|])
        match o with
        | Some o -> Expr.Call(o,newMI,args)
        | None -> Expr.Call(newMI, args)
    | _ -> failwith "Called on a non-Call Expr"

let makeArrayExpr (length : int) (t : Type) =
    <@@ Array.zeroCreate<obj> length @@>
    |> replaceTypeOnCall t

We require an additional helper function to modify this Expr.Call to use the correct System.Type. The above example works fine, and is not too big of a pain, because there is only one call to replace, and one type parameter to replace.

An expression which adds two ints together is simple:

let add (x : Expr) (y : Expr) = <@@ (%% x : int) + (%% y : int) @@>

An expression which adds two t expressions is not possible to write using quotations.

let add (x : Expr) (y : Expr) (t : Type) =
    let minfo = t.GetMethod("op_Addition")
    Expr.Call(minfo, [x; y])

This proposal is to allow splicing of type values into quotations, using ~% operator like so:

let makeArrayExpr (length : int) (t : Type) =
    <@@ Array.zeroCreate<~%t> length @@>

This example is simple, it instantiates the generic Array.zeroCreate MethodInfo with the System.Type t.

let testEquals (x : Expr) (y : Expr) (t : Type) = <@@ (%% x : ~%t) + (%% y : ~%t) @@>

This example is somewhat more complicated. We want to construct an expression which corresponds to the addition of x and y, using a + operator. This would desugar to asking for the op_Addition MethodInfo from the given t, and building an Expr.Call using this method.

This process can obviously fail at runtime, but we are already in a world where the %% operator exists (which can obviously fail at runtime).

Under the hood, this ~% operator maps an expression to a free type variable. For unification to succeed, each term variable t must map to the same type variable 't.

Naturally, this formulation should complain if one of these variables escapes the scope.

<@ (%%x : ~%t) @>
>>> Type variable 't escapes this scope.

Resolving operators is done as follows:
Simple constraints such as: ^t : (static member op_Addition : ^t * ^t -> ^t) can be translated to:

t.GetMethod("op_Addition")

"Or" constraints such as (^s or ^t) : (static member op_Addition : ^s * ^t -> ^t) can be translated to:

match s.GetMethod("op_Addition"), t.GetMethod("op_Addition") with
| null, null -> failwith "No method found"
| mi, null -> mi
| null, mi -> mi
| mi, mi -> failwith "Ambiguous"

Pros and Cons

The advantages of making this adjustment to F# are:

Much easier handling of generic functions and generic operators in quotations involving types known only at runtime.

The disadvantages of making this adjustment to F# are:

Cost of implementation.
Another operator to learn.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M/L

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:
[x] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
[x] I have searched both open and closed suggestions on this site and believe this is not a duplicate
[x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:
[x] This is not a breaking change to the F# language design
[x] I or my company would be willing to help implement and/or test this

@baronfel

This comment has been minimized.

Collaborator

baronfel commented May 17, 2018

This is a pain point that I ran into with SwaggerProvider. We generate unknown types from schemas, and then create typed Async-returning functions to make HTTP calls to retrieve those types.

This required definition of cast trampolines for Task and Async (as shown here: https://github.com/fsprojects/SwaggerProvider/blob/dev/src/SwaggerProvider.Runtime/RuntimeHelpers.fs#L14-L22) and then coercion of types using those trampolines(as shown here: https://github.com/fsprojects/SwaggerProvider/blob/dev/src/SwaggerProvider.DesignTime/OperationCompiler.fs#L234-L238).

@dsyme

This comment has been minimized.

Collaborator

dsyme commented May 18, 2018

Yes, I appreciate the issue and it's been a missing feature of quotations since the start. I think I decided not to support it initially because it would only be in untyped quotations.

@TobyShaw

This comment has been minimized.

TobyShaw commented May 18, 2018

It should work in typed quotations, as long as the type parameter doesn't escape the scope:

val serialise<'a> : 'a -> byte[]

let serialiseExpr (x : Expr) (t : Type) : Expr<byte[]> =
    <@ serialise<~%t> (%%x : ~%t) @>
@7sharp9

This comment has been minimized.

Member

7sharp9 commented Jun 12, 2018

Im forever doing GetGenericMethodDefinition().MakeGenericMethod and derivatives
+100000

@7sharp9

This comment has been minimized.

Member

7sharp9 commented Jul 17, 2018

Does this need an RFC?

@7sharp9

This comment has been minimized.

Member

7sharp9 commented Sep 3, 2018

@TobyShaw Is there a implementation for any of this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment