Skip to content
Bryan Edds edited this page Oct 26, 2025 · 32 revisions

Sometimes people find custom operators to be a stumbling block when using new F# APIs. We have worked hard to try to minimize the amount of custom operators users need to learn to use Nu. However, in order to make the API practical to succinctly author code with, we had to keep some. Here's a cheat-sheet of Nu's operators, starting with the operators you should familiarize yourself with first.

Operators for ImSim

    /// Specifies a static ImSim argument that DOESN'T reinitialize on code reload.
    let (|=) (lens : Lens<'a, 's>) (value : 'a) =
        { ArgType = InitializingArg; ArgLens = lens; ArgValue = value } : 's ArgImSim

The |= operator will define the property of a simulant as some unchanging value in an ImSim context. Even on code reload, its value will not change.

    /// Specifies a static ImSim argument.
    let (.=) (lens : Lens<'a, 's>) (value : 'a) =
        { ArgStatic = true; ArgLens = lens; ArgValue = value } : 's ArgImSim

The .= operator will define the property of a simulant as some unchanging value in an ImSim context, but it will update once on code reload.

    /// Specifies a dynamic ImSim argument.
    let (@=) (lens : Lens<'a, 's>) (value : 'a) =
        { ArgStatic = false; ArgLens = lens; ArgValue = value } : 's ArgImSim

The @= operator will keep the property of a simulant synchronized with a changing value in an ImSim context. You might think of it like a data binding if you're familiar with MVC or MVVM terminology.

Operators for MMCC

    /// Define a static property equality that doesn't reinitialize on code reload.
    let (!=) (lens : Lens<'a, 's>) (value : 'a) : 's DefinitionContent =
        PropertyContent (PropertyContent.make InitializingProperty lens value)

The != operator will define the property of a simulant as some unchanging value in an MMCC context. Even on code reload, its value will not change.

    /// Define a static property equality.
    let (==) (lens : Lens<'a, 's>) (value : 'a) : DefinitionContent =
        PropertyContent (PropertyContent.make true lens value)

The == operator will define the property of a simulant as some unchanging value in an MMCC context, but it will update once on code reload.

    /// Define a dynamic property equality.
    let (:=) (lens : Lens<'a, 's>) (value : 'a) : DefinitionContent =
        PropertyContent (PropertyContent.make false lens value)

The := operator will keep the property of a simulant synchronized with a changing value in an MMCC context. You might think of it like a data binding if you're familiar with MVC or MVVM terminology.

    /// Define an event signal.
    let (=>) (eventAddress : 'a Address) (signal : Signal) : DefinitionContent =
        EventSignalContent (Address.generalize eventAddress, signal)

The => operator will cause an event to produce a signal (either a Message or a Command) that will be consumed by an MMCC context.

    /// Define an event handler.
    let (=|>) (eventAddress : 'a Address) (callback : Event<'a, 's> -> Signal) : DefinitionContent =
        EventHandlerContent (PartialEquatable.make (Address.generalize eventAddress) (fun (evt : Event) -> callback (Event.specialize evt) :> obj))

The =|> operator will cause an event to produce a signal (either a Message or a Command) that will be consumed by an MMCC context. Additionally, it allows the Event<'a, 's> value or its data to be passed into the constructed signal.

Operators for Composing Addresses

        /// Concatenate two addresses of the same type.
        static member (-|-) (address : 'a Address, address2 : 'a Address) : 'a Address = Address.acat address address2

        /// Concatenate two addresses, forcing the type of first address.
        static member (-->) (address : 'a Address, address2 : 'b Address) : 'a Address = Address.acatff address address2

        /// Concatenate two addresses, forcing the type of second address.
        static member (<--) (address : 'a Address, address2 : 'b Address) : 'b Address = Address.acatsf address address2

These are the various operators used to conveniently compose new addresses. Generally, if you stick to the ImSim or MMCC APIs, you won't have to compose address much yourself.

    /// Concatenate an address with a simulant's address, forcing the type of first address.
    static member (-->) (address : 'a Address, simulant : #Simulant) =
        if isNull (group :> obj) then Address.anonymize address else acatff address simulant.SimulantAddress

The --> operator also has a convenience overload for composing new addresses from simulant handles. You will use this when defining new types of events for your simulants like so -

    [<RequireQualifiedAccess>]
    module Events =
        let ItemSelect = stoa<string> "Item/Select/Event"

    type Entity with
        member this.ItemSelectEvent = Events.ItemSelect --> this

Operator for Constructing Simulant Handles

    static member (/) (simulant : #Simulant, childName) =
        ...

The / operator overload makes it easy to construct hierarchies of simulant handles like so -

[<RequireQualifiedAccess>]
module Simulants =
    let Gameplay = Game / "Gameplay"
    let Scene = Gameplay / "Scene"
    let Player = Scene / "Player"

F#'s built-in operators

A list of F#'s built-in operators and symbols can be found here. Many of these will be familiar to C++/C# developers. For the purposes of working with Nu, you can safely disregard quotations, dynamic lookup, nullables and reference cell operators. We also avoid using most of the function operators, except |>, >> and occasionally <<.

Clone this wiki locally