fsharp / fsharp/fslang-suggestions
Splice types into Quotations
- Dominant language
- No language data
- Stars
- 373
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
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:
```fsharp
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 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:
```fsharp
let add (x : Expr) (y : Expr) = <@@ (%% x : int) + (%% y : int) @@>
```
An expression which adds two t expressions is not possible to write using quotations.
```fsharp
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:
```fsharp
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`.
```fsharp
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.
```fsharp
<@ (%%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:
```fsharp
t.GetMethod("op_Addition")
```
"Or" constraints such as `(^s or ^t) : (static member op_Addition : ^s * ^t -> ^t)` can be translated to:
```fsharp
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](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
[x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) 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
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.