/// # Sample
/// Specializations
///
/// # Description
/// Q# allows specialized implementations. Operations in Q# can implicitly
/// or explicitly define adjoint and/or controlled versions.
/// Q# employs symbolic computation that can automatically generate the
/// corresponding adjoint and controlled implementations for a particular 
/// body implementation.
namespace MyQuantumApp {

    /// The adjoint, controlled and adjoint-controlled specializations are implicitly
    /// generated for the `DoNothing` operation that declares supports for these
    /// specializations using the `is` keyword followed by the union of the supported
    /// specializations (`Adj + Ctl`).
    operation DoNothing() : Unit 
        is Adj + Ctl { }

    /// Here, the specializations hvae been explicitly defined. 
    /// In the following example, the declaration for an operation SWAP,
    /// which exchanges the state of two qubits q1 and q2, declares an
    /// explicit specialization for its adjoint version and its controlled
    /// version. While the implementations for Adjoint SWAP and Controlled
    /// SWAP are thus user-defined, the compiler still needs to generate 
    /// the implementation for the combination of both functors (Controlled
    /// Adjoint SWAP, which is the same as Adjoint Controlled SWAP).
    operation SWAP (q1 : Qubit, q2 : Qubit) : Unit
        is Adj + Ctl { 

        body (...) {
            CNOT(q1, q2);
            CNOT(q2, q1);
            CNOT(q1, q2);
        }

        adjoint (...) { 
            SWAP(q1, q2);
        }

        controlled (cs, ...) { 
            CNOT(q1, q2);
            Controlled CNOT(cs, (q2, q1));
            CNOT(q1, q2);            
        } 
    }

    /// The main function cannot be Adj or Ctl. 
    @EntryPoint()
    operation Main() : Unit {

        // We invoke specializations using functors at the call site. 
        // In order to call these operations and their specializations, we need
        // to allocate some qubits.
        use (q1, q2) = (Qubit(), Qubit());

        // We can call `SWAP` in the normal way, without any functors or
        // specializations.
        SWAP(q1, q2);

        // We use the functor `Adjoint` on the operation `SWAP` to invoke the
        // adjoint specialization of `SWAP`.
        Adjoint SWAP(q1, q2);
        
        // Now,we will use the `Controlled` functor. To use this functor, we
        // need some control qubits. 

        // The `Controlled` functor invokes the control specialization of the
        // operation. Note that the control specialization has a different 
        // signature than the regular operation: first, the control qubits;
        // second, the parameters for the regular operation.
        // For a singly controlled invocation, only pass one control qubit
        // in the first array.
        use ctl = Qubit();
        Controlled SWAP([ctl], (q1, q2));

        // For a multi controlled invocation, pass an array of multiple qubits
        // in as the first argument.
        use ctls = Qubit[3];
        Controlled SWAP(ctls, (q1, q2));
    }
}