This is purely a mental exercise to play around with various programming semantics and see what my ideal language would look like. It might go as far as prototyping to learn more about the DLR.
Quick notes about Promise, a dynamically typed language with optional, minimum capability annotation for object discovery and simple "type" checking
Design Notes
- Curly brace block style
- case-insensitive
- Hashes and Arrays are first class constructs, i.e. can be created with just {} and []
- Does that make determination between blocks and hashes too ambiguous?
- JSON is the native serialization/de-serialization format
- Automatically serializes/initializes public properties
- requires custom overrides to serialize private members (i.e. serialization assumes DTOs)
- Perl style regular expressions are first class constructs
- Smalltalk message style method dispatch
- Impossible to get null-reference, any dispatch not finding a matching listener returns nil
- Everything is an object
- operators are just special named methods on objects
- a + b => a.+(b)
- So it should be possible to call instance method arg
- No nulls, just nil
- nil is a valid object which itself accepts messages, always returning nil
- i.e. foo.bar.baz.ping will return nil if any part is nil
- nil is a singleton instance has interface void
- Classes are object prototypes, i.e. instances can be changed at runtime
- Classes can be changed as well
- Different namespaces can import additional methods or re-write existing ones, add mix-ins
- Lambdas similar to C#
- Methods are defined by assigning a lambda to a class Slot
- Unlike C# has explicit separator for return value since there is no Action<> vs. Func<> types
- Argument-less lambdas with a return value can be returned in the place of a value and accessing the lamba executes it (since lambdas can be invoked without parantheses)
- This means that a return value expected to be a value can actually be a continuation producing that value
- A type is just capability declarations
- Classes are not types and types cannot be instantiated
- Used to declare that capabilities expected of an instance cast to a type
- All typing is duck typing, i.e. a class does not "implement" a type
- The instance doe not have to statically declare the methods described, as long it has a wildcard signature that will receive the message required
- Any definition also creates a matching type of the same name for non-generic signatures
- An argument of type Song does not mean an instance of class Song, but any object that provides the same capabilities expressed by the implicit or explicit type Song
- type and class names do not collide, so if you create a class with a wildcard method (which won't show when inspecting the type of the same name) but you have certain signatures you want to support for discovery, you can create the type, shadowing the class, manually
- Methods
- only public and protected
- methods really are just lambdas that capture the class scope and are attached at a named Slot of the class
- Calling conventions:
- method value1 value2 valueN
- method(value1, value2, valueN)
- method{ argN: valueN, arg1: value1}
- Signature can initialize value defaults
- Trailing arguments can be left out, or in case of hash calling convention any key can be left out
- arguments can optionally declare interface types
- special arguments that have to be last in the declaration
- *arg: captures all extra arguments in ordered list call
- #arg: captures all extra key/value pairs in hash style call
- can use both in a single method signature
- Can define a wildcard method to catch messages for methods that are not defined
- wildcard signature must declare both *arg and #arg slots
- Ruby style symbolic identifiers, i.e. :foo
- Classes
- single inheritance, many mix-ins
- fields are always protected
- Ruby/C# style "properties" for field access
- Ruby style automatic read-only and read/write properties supported
- C# style indexer properties supported
- Only single "constructor" using key/value arguments
- Can define Friend classes to provide access to internals
- Naming defines class member:
- __foo => class field (protected)
- _foo => instance field (protected)
- Classname.foo => class method (public)
- this.foo => instance method (public)
- foo => implicit format for method
- !foo => protected modifier for method
- C# style external iterators
- yield keyword allowed for methods Enumerable return values
- C# looking generics, but only for templating interface types
- void is a valid built-in type for generics
- Mix-ins
- extension methods with access to class internals
- Mix-in constructor defines field name mapping between mix-in and destination class
- Language level Dependency Injection
- Classes declare constructors but there is no way to "instantiate" classes
- All instance resolution happens through Class{ args } call, which resolves the instance from the context
- Allow scope and "type" annotation on classes for auto-registration
- Manual registration DSL
- injection context responsible for class creation and fills in missing parameters, when possible
- default scope is factory (i.e. new instance per call)
- Nested and symbolically named contexts (a new child context can be created at any time)
Implementation
- Built using the DLR on top of the CLR
- Since instances can have capabilities dynamically attached there needs to be a way to mark instances as "fuzzy" or code blocks as "dynamic" so that static analysis can be done for the majority of code
- Need some clear disambiguation rules for determining what is an instance, method and argument in a series of whitespace separated keywords
- Basically need to have a context-specific literal searchtree
- Stackless?
- Hide threads, only deal with Tasks?
Syntax Scratchpad
Lambdas
Format:
[( [<inputArg1>, ..., <inputArgN>] [ | <type>] ) =>] { <expression> };
<inputArg>: [<type>] <argName> [ = <default value expression> ]
A lamba can have 0 or more input arguments and 0 or 1 output argument. Unless the output argument has a type, the trailing | can be omitted, since all lambdas return a value. A lambda not declaring a return type implicitly returns the void type, i.e. the nil value.
The return keyword is optional. The value of the last expression is returned by default. return is also used to exit an expression before the end.
// input less lambda's can omitted the input/output declaration and =>
var a = { var x = 1; x++; };
print a; // => 1
print a; // => 2
// lambda's can capture variables from current scope
var x = 1;
var a = { x+1; };
print a; // => 2;
x = 5;
print a; // => 6;
//lambda's only need to declare return value if it's a capability
var x = (name|Song) => { JukeBox.Find(name); }
var song = x("Fett's Vette");
Type declaration
type TaxCalculator {
Calculate(State state, amount|Numeric);
}
Method definition
Defining a method is basically assignment of a lambda to a named slot on a class or instance. However since named slots can take an rvalue and because slots can take more than one lambda (for overloads), assignment requires that the slot name is preceeded by # for disambiguation.
// lambda
(Song song, rating=5|int) => {
return 1;
};
// defining a class method
Song.#Find = (name|Song) => {
return JukeBox[name];
};
// defining an instance method
var song = Song();
song.#Play = () => { };
// for class, the #, = and => of lambda assignment can be omitted inside the definition scope
class Song {
// defining a class method
Song.Find(name|Song) {
return Jukebox[name];
}
// defining an instance method (the this is optional)
this.Play() { ... }
// define a protected instance method with no arguments and a dynamic return value
!AmProtected() { ... }
// Class property method
Name=(name) { ... }
}
Re: allocation then initialization, i agree. I don't want to have lots of constructors like C# has. Of course the whole allocation and initialization is out of the hand of the caller and in the hands of the DI context.
Re: protected: I like the ! suggestion. Using it