Defining a Base for Push Types

Let’s take a look at how the base from which all Push types derive is defined.

namespace push.types
 
[<AutoOpen>]
module Type =
    
    open System.Reflection
    open push.exceptions
    open System.Diagnostics
    open System
 
    let Random = Random (int DateTime.UtcNow.Ticks)
 
    [<DebuggerDisplay("Value = {Value}")>]
    [<StructuredFormatDisplay("{StructuredFormatDisplay}")>]
    [<AbstractClass>]
    type PushTypeBase () = 
        [<DefaultValue>] 
        val mutable private value : obj
 
        [<DefaultValue>]
        val mutable private myType : string
 
        new (v) as this = PushTypeBase()
                            then this.value <- v
 
        member t.Value with get() = t.value
 
        static member private GetMyType (me : #PushTypeBase) =
            if System.String.IsNullOrEmpty(me.myType)
            then
                me.myType <- (me.GetType().GetCustomAttributes(typeof<PushTypeAttribute>, false).[0] :?> PushTypeAttribute).Name
            me.myType   
 
        // if something more than default action is necessary
        // this should be overridden
        abstract member Eval : PushTypeBase
        default t.Eval = t
                
        abstract member MyType : string with get
        default t.MyType with get () = PushTypeBase.GetMyType(t)
 
        abstract member isQuotable : bool with get
        default t.isQuotable with get () = false
 
        member t.Raw<'a> () =
            match t.Value with
            | πŸ˜• 'a as raw -> raw
            | _ -> failwithf "could not convert to the right type"
 
        abstract StructuredFormatDisplay : obj
        default t.StructuredFormatDisplay =
            box t.Value
 
        override t.ToString() =
            t.Value.ToString()
 
        abstract Parser : ExtendedTypeParser with get
    
    and     // override this delegate to parse extended types
        ExtendedTypeParser = delegate of string -> PushTypeBase

Push types are represented (in all .NET languages) by classes derived from PushTypeBase. This is an abstract class and the listing above shows its definition.
Two properties are essential for all classes, deriving from PushTypeBase:
β€’ Value – an object that represents the actual value of the instance of this type
β€’ MyType – name of the Push type (i.e. INTEGER) that this object represents.

Implementation of the Value property is straightforward. MyType is slightly harder because it needs to be bootstrapped for each individual push type. In other words, once the instance of this type is created, we need to actually discover its Push type.

An easy way out would be to just let the developer set this property at object instantiation, but since Push types are already decorated by attributes, that specify their names, which is necessary for dynamic discovery, I did not want to introduce code duplication.

So, the function that drives this virtual property, GetMyType, actually takes an instance of the PushTypeBase derived class:

static member private GetMyType (me : #PushTypeBase) =

The β€œ#” in type annotation means β€œany class, that has #PushTypeBase somewhere in its derivation hierarchy.

This function actually looks at the instance of the class it is given and extracts the name of Push type from its custom attributes. It only does that once, on first call. In the implementations of PushTypeBase it is necessary to define a variable that would refer to an instance of the same type. Kind of ugly, but avoids code duplication. MyType can then be accessed through this variable: Me.MyType

[<PushType("FLOAT")>]
type Float =
    inherit PushTypeBase
 

    static member Me = Float()

    override t.ToString() =
        t.Raw<float>().ToString("F")

   

Another function to note is Raw(). This property unboxes the actual value of the Value property and presents it as an F# object or value. A type parameter is necessary for that. For instance, the FLOAT type can return string representations of its objects by overriding the .NET object ToString() functions as shows in the above example.

Noteworthy Stuff

:
1. [<AutoOpen>] attribute at the module level instructs F# to open the module automatically, once its containing namespace is being open. I find it very convenient.
2.

    [<DebuggerDisplay("Value = {Value}")>]
    [<StructuredFormatDisplay("{StructuredFormatDisplay}")>]
    [<AbstractClass>]

Decorating classes with DebuggerDisplay and StructuredFormatDisplay attributes, and then implementing custom displays saves a lot of time debugging and should generally be done for most if not every object.

Here is a snapshot taken during execution of a simple test:

        [TestMethod]
        public void DoSimple()
        {
            var prog = "(CODE.QUOTE (5 INTEGER.+) CODE.QUOTE (3 5 INTEGER.*) CODE.DO CODE.DO)";
            Program.ExecPush(prog);
 
            Assert.AreEqual(20, TestUtils.Top<long>("INTEGER"));
        }

This program does the following:

1. Pushes (5 INTEGER.+) on the CODE stack
2. Pushes (3 5 INTEGER.*) on the CODE stack
3. Executes the program on top of the CODE stack
   (the result is 3 *5 pushed to INTEGER stack)
4. Executes the program on top of the CODE stack
   (the result is 5 pushed to INTEGER tack and then
   5 + 15 pushed on top of the INTEGER stack.

Here is a snapshot of the interpreter state taken during this execution.
Because of DebuggerDisplay and StructuredFormatDisplay for various objects, the state of the INTEGER stack can be clearly observed.

Debugger Watch Window

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.