positron-lang / spec

Positron Language Specification
4 stars 1 forks source link

Symbols #19

Open igorbonadio opened 9 years ago

igorbonadio commented 9 years ago

Lets talk about Symbols

igorbonadio commented 9 years ago

Marking as opened to #18

renatocf commented 9 years ago

Marking as opened to #18

vinivendra commented 9 years ago

Ok. What do you mean by symbols? Is this that ruby feature you mentioned?

igorbonadio commented 9 years ago

Yes. Symbols are like lightweight strings.

I think it is similar to selectors in objc.

vinivendra commented 9 years ago

No, i think seletors are a whole different thing hahahaha

Are symbols used just at compile time, or can they me examined at runtime also? For instance, is there any way a function can receive a symbol and print it to the screen?

renatocf commented 9 years ago

As far as I know, yes, it's possible. In fact (@igorbonadio can say it much better than I do), many people use them in Ruby as hash keys (instead of strings). The greatest difference between them is that two String's can be different objects, thought they may have the same value. This leads to confusions as in Java, where we could have:

class PlayingWithStrings
{
    public static void main(String[] args) {
        String s1 = "Some text";
        String s2 = "Some text";
        String s3 = s1;

        System.out.println(s1 == s2); // => false; different string *objects*
        System.out.println(s1.equals(s2)); // => true; equal string *values*

        System.out.println(s1 == s3); // => true; references point to the same object
        System.out.println(s1.equals(s3)); // => true; equal string *values*
    }
}

Symbols solve this problem: in Ruby, any time we reference :symbol, it will be the same object. It works as a good replacement for C/C++/Java enum's - instead of declaring for every class in which they would be needed, they are widely available and can be as a selector (perhaps this is a motivation for a name like that in ObjC) in classes.

In Positron, we could use them + match-case syntax to create factory methods:

def shape_factory(shape : Symbol) -> (res : Shape)
  res = match(shape)
    case :square => Square(side: 2)
    case :rectangle => Rectangle(height: 2, width: 4)
    case :triangle => Triangle(base: 2, height: 4)
  end
  throw Exception("Should be a valid symbol!")
end

This is an indication. I don't remember if we have an otherwise, case(nil) or something... Pattern Match spec has nothing about it. Anyway, as a tuple of one object is equivalent to a simple object, I think this would work without changing anything. And using the fact everything is an expression, it's simple to create factory methods like the one above.

What do you think? I'm just not sure how we could insert them in the language. Do we need a special support for :something such that the compiler will identify it should always reference the same object? For now, I believe so. And I also think : in the beginning of a symbol is good enough (and has an immediate identification with Smalltalk/Ruby).

vinivendra commented 9 years ago

Ok, I think I get the basics. However, I wanted to ask: what advantages do symbols have over enums?

I ask that because I think @renatocf's example would be better if we used an enum instead. For one thing, we would know exactly what kinds of shape are allowed: this would mean we could throw a warning if a case had an invalid value (for instance, if there was a typo or if a value had been deleted) and we could enforce cases for all values (so that if a value was added, the compiler warnings could tell the programmer where to change his code to adapt to the new value).

I understand printing symbols could be hefty in debugging, though a good debugger might print enum names too. So, what are the advantages of symbols?

vinivendra commented 9 years ago

Oh, and "selectors" in ObjC is basically just another word for "methods", it's got nothing to do here :)

igorbonadio commented 9 years ago

Yes. It is like an enum. The only difference is that you dont need to explicitly define the enum type. You can see it as if the compiler could define all possible kinds of enums.

Ah, and you can convert a string to a symbol

"blah".sym == :blah
vinivendra commented 9 years ago

What I like about enums here is one thing: compiler guarantees. As I understand it, if you have a closed set of enums, there are a lot of advantages that the compiler can bring (like the ones I mentioned above).

However, I can see a few advantages symbols might bring: for example, being able to "build" the symbol from a set of instructions and then use it in code. Personally, though, I'm not really a fan of this "runtime programming" capabilities.

Do you think these two ideas are mutually exclusive? I know they aren't necessarily so, but still, would you want to drop enums if we took on symbols?

Maybe there's a way we can unite both paradigms into one better solution, that might be worth looking into.

Kazuo256 commented 9 years ago

In Java, you can find Enum by giving strings. It is a matter of making the compiler inject a symbol table in the compiled code for each enum defined.

igorbonadio commented 9 years ago

@vinivendra is right. I think, for a statically typed language, it is better to have some compiler guarantees. For example:

def shape_factory(shape : Symbol) -> (res : Shape)
  res = match(shape)
    case :square => Square(side: 2)
    case :rectangle => Rectangle(height: 2, width: 4)
    case :triangle => Triangle(base: 2, height: 4)
  end
  throw Exception("Should be a valid symbol!")
end

The shape definition doesn't express what kind of shapes we can build... Maybe it is better:

enum ShapeType = Square | Rectangle | Triangle

def shape_factory(shape : ShapeType) -> (res : Shape)
  res = match(shape)
    case ShapeType::Square => Square(side: 2)
    case ShapeType::Rectangle => Rectangle(height: 2, width: 4)
    case ShapeType::Triangle => Triangle(base: 2, height: 4)
  end
end
igorbonadio commented 9 years ago

We could define this enums with parameters:

enum Shape = Square(side: 2) 
           | Rectangle(height: 2, width: 4)
           | Triangle(base: 2, height: 4)

And use it in patter matching:

def area(shape : Shape) -> (res : Int)
  res = match(shape)
    case Shape::Square(side: s) => s*s
    case Shape::Rectangle(height: h, width: w) => h*w
    case Shape::Triangle(base: b, height: h) => b*h/2
  end
end

The enum would be like a "string with some value".

What do you think?

vinivendra commented 9 years ago

Those are some really interesting ideas. I think one idea that is worth mentioning is (you guessed it) swift's implementation of enums. Basically, enums would be just like enums in C, except we can specify a type for them. That means they don't have to be integers, they can also be strings, etc. It's an idea that gives the programmer a lot of flexibility.

enum Shape: String
    Square = "Square"
    Circle = "Circle"
end

enum Bool: Integer
    true = 1
    false = 0
end

I don't think that "namespace" is necessary though. If we know the value being tested is a Shape, we know exactly what values it can assume - only the ones in the Shape declaration.

vinivendra commented 9 years ago

Perhaps we can create some special default cases, just as an added sugar. If the enum is defined as an integer or a natural, the values don't need to be specified, they just go from 0 to n. Similarly, if the enum is a String, the values might be the names themselves as Strings.