Saturday 31 October 2020

Top and Bottom Type

When reading this post by Axel Rauschmayer about any being a "top type" in TypeScript, I found particularly interesting one of the comments, that argues that it's both a top and a bottom type:

1. any is like a top type in that you can assign values of all other types to it: let a: any = x works whatever type x has.
2. any is like a bottom type in that you can assign values with an any type to all other types: let a: any; let x: T = a is allowed for all types T (except never).

This made me revisit the behaviour of the dynamic keyword in C#. It's clear that it behaves as a top type, but I was a bit dubious about how the compiler dealed with it and to what extent it could be also considered a bottom type. Some code to the rescue:



    interface ITalkative
    {
        string SayHi();
    }
    
    class Person: ITalkative
    {
        public string SayHi()
        {
            return "Bonjour";
        }
    }

    class Printer
    {
        public static void Print(ITalkative t)
        {
            Console.WriteLine("[" + t.SayHi() + "]");
        }
    }

    class Cat
    {
        public string SayHi()
        {
            return "Miau";
        }
    }

 
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Started");
            dynamic ob = new Cat();

            try{
                Printer.Print(ob);
            }
            catch(Exception ex){
                Console.WriteLine("1. " + ex.Message);
                //The best overloaded method match for 'Dynamic.Printer.Print(Dynamic.ITalkative)' has some invalid arguments
            }
            
            try{
                Person p = ob;
            }
            catch(Exception ex){
                Console.WriteLine("2. " + ex.Message);
                //Cannot implicitly convert type 'Dynamic.Cat' to 'Dynamic.Person'
            }

            try{
                Printer.Print(ob as Person); //casting returns null and then Print fails
            }
            catch(Exception ex){
                Console.WriteLine("3. " + ex.Message);
                //Object reference not set to an instance of an object.
            }

            try{
                Person p1 = ob as Person;
            }
            catch(Exception ex){
                Console.WriteLine("4. " + ex.Message);
            }

  
        }
    }

I'm particularly interested in the first case. I was not sure if the compiler would allow that, but it does. So the compiler allows me to invoke a method that expects ITalkative with just a "dynamic" value, so yes, we can say that dynamic is a bottom type. Notice however that dynamic in C# has nothing to do with Duck Typing (or TypeScript's structural typing), so it's obvious that this will fail at runtime. The method invocation Printer.Print fails cause the runtime checks (it's different from a cast error) if the parameter implements ITalkative, as that is not the case, we get a The best overloaded method match for 'Dynamic.Printer.Print(Dynamic.ITalkative)' has some invalid arguments exception.

A decade ago (puff...) I had posted about Duck Typing in C# and dynamic, but probably I was thinking that we would already get a compiler error in this situation, rather than a runtime one.

No comments:

Post a Comment