Joh
Joh

Reputation: 2380

How to define and use % as a prefix operator?

type T() =
    static member (~%)(t : T) = t

let t = T()
let t' = %t // FAILS

The error message says t was expected to be of type Quotation.Expr<'a>. % is a supposedly valid prefix operator, but is it possible to actually use it?

Upvotes: 5

Views: 344

Answers (2)

Stephen Swensen
Stephen Swensen

Reputation: 22297

The reason why you are seeing this behavior is because F# does not define (~%) with static constraints like most top-level operators. It is defined as a function Quotations.Expr<'a> -> 'a. Hence, the (~%) function (which is an alias for op_Splice) you defined on type T is not resolved by uses of the top-level (~%) operator.

You can see this by the following FSI interaction:

> <@ (~%) @>;;

  <@ (~%) @>;;
  ^^^^^^^^^^

C:\Users\Stephen\AppData\Local\Temp\stdin(5,1): error FS0030: Value restriction. The value 'it' has been inferred to have generic type
    val it : Expr<(Expr<'_a> -> '_a)>    
Either define 'it' as a simple data term, make it a function with explicit arguments or, if you do not intend for it to be generic, add a type annotation.

Thus if we redefine the top-level (~%) operator as follows, then your example will compile without error:

let inline (~%) (x : ^a) = (^a : (static member op_Splice : ^a -> 'b) (x))

but do note that quotation splicing will no longer work:

let x = <@ 3 @>
<@ %x @>
----^
error FS0001: The type 'Expr<int>' does not support the operator '~%'

that's because the original definition of (~%) is treated specially by the compiler for quotation splicing. Indeed, you can see in the Expr and Expr<'T> signatures that those types do not define any operators at all, let alone op_Splice.

You can see similar results with && and || infix operators. Which can be redefined (mapping to op_BooleanAnd and op_BooleanOr), but unless they are, they are treated specially by the compiler.

Upvotes: 5

Tomas Petricek
Tomas Petricek

Reputation: 243061

I'm not exactly sure why the % operator behaves like this, but you can redefine it using global let binding:

let (~%) a = -a
%10

If the operator cannot be defined as static member (I'm not sure if that's the case, or if I'm just missing something), you can still define an inline definition that invokes some static member of an object. This should give you essentially the same functionality:

// Instead of defining static member '%', we define static member 'Percent'
type T() =     
    static member Percent(t : T) = t 

// Inline definition of '~%' that calls the static member 'Percent' of an object
let inline (~%) (x : ^T) = (^T : (static member Percent : ^T -> 'R) (x))  

// Now you can use the '%t' syntax to invoke the static member
let t = T()     
let t' = %t

Background: In F# quotation code, it is used for "splicing" of expressions into another expression (to build an expression that is composed from another, previously defined expression). The error message suggests that the compiler did not see your definition.

let two = <@ 2 @>
let oneAndTwo = <@ 1 + %two @>

Upvotes: 4

Related Questions