As a result I've been binge reading some of the new v2 documentation for the past days or so. I have to say, I am really impressed with how clean and straightforward v2 syntax looks now, how many of the old quirks are gone, how so many things are much better integrated with objects, and how built-in functionality no longer does clunky or unexpected things (for the most part I guess). I'm actually tempted to have fun with AHK scripting again, which is something I haven't felt in a long time. There are a couple of things that have come to mind about the current state of v2 though, which I hope to express here:
Syntax
- Now that the old command syntax is gone (finally!) and AutoHotkey is now able to detect line continuations by enclosure, continuation sections feel really out of place. The main usage for them has always been multiline strings and they're basically a preprocessor stage with no intrinsic semantic value at all. They can sometimes get in the way of expression syntax (thankfully alleviated by the ) rule), and they have terse and hard to read options that can change basic language rules such as escape characters. I feel like there should be an actual syntax designed explicitly for multiline strings (think of Python's """ or C++'s raw string literals), and it would supersede continuation sections in v2. On top of that, continuation sections make it considerably difficult to write an accurate syntax highlighting parser/lexer for the v2 language (unless we cheat a bit and treat all continuation sections as strings, like the old Scintilla AHK1 lexer did).
- I'm glad to see my old proposal for @Func function-reference syntax still being considered.
- Closures, missing parameters, and fat arrow functions rock. I can't wait to write scripts using them.
- At some point there was a string variable interpolation syntax, but that was removed. Presumably this was due to the fact that Format is superior and should be used instead. However some popular languages do have variable interpolation as a way to cut down on syntactic noise stemming from repeated concat operators. If variable interpolation in strings is reintroduced then we can get rid of auto-concat, which can be seen as kludgy syntax that results in unexpected/unintuitive results (Function(Param) works, Function (Param) does not).
- Some #Defines seem to still accept parameters resembling the old command syntax, most notably #Include. I think the latter needs some kind of redesign, probably to make it more useful in its goal of allowing the usage of external libraries written in script (and some kind of solution needs to be found for libraries providing classes).
- Overall I like the new design, the type hierarchy is much simpler, the object invocation protocol is more straightforward and with less caveats. The __Item property handling obj[...] syntax is genious.
- Building on the above, I think it should be more or less possible to add methods or properties for other operators (i.e. + -> __Add(rhs) and so on) or implicit conversions (i.e. __String, __Integer, ...). Current design is flexible enough that this can be left for post-v2 though.
- __Set is currently (this, Name, Params*, Value). Since only the last parameter can be variadic and the Value of a set operation is supposed to be mandatory, I think for consistency and ease of use it should be (this, Name, Value, Params*).
- Base vs Prototype can be a bit confusing and it's taking me some time to understand this stuff. I know the former is the base object we all know and love from v1 which is used during object operations, and I can understand that the base object used in all instances of a class is called Prototype and it is stored as the Prototype property of a class (however the object for the class itself is in fact just an instance of Class so its base ought to be Class.Prototype, right?). There's some potentially confusing wording in the Class page: "Object is based on Class.Prototype, which is based on Object.Prototype" (at first this read to me like "Object inherits from Class which inherits from Object"...), and it can also seemingly appear to be contradicted by this bit in the Object page: "All instances of Object are based on Object.Prototype, which is based on the Any prototype", but of course the object that represents a certain class is a separate thing from the prototype used as the instances' base; which makes sense. I'm not sure what happens in subclasses though (i.e. class Sub extends Main). If I had to guess, I'd assume that Sub.Base == Main and Sub.Prototype.Base == Main.Prototype and Main.Base == Class.Prototype because the documentation seems to imply that you can access static members (fields/methods) of a class from any of its subclasses.
- Despite the Array/Map separation, there is a base Object type which... is essentially two associative arrays put together (one for "own properties" and "own methods"). From what I understand, this base Object type is the backbone of all objects (assuming non-COM of course), and it is used to construct Prototype objects for the various classes, as well as the class objects themselves. However (and this is the confusing part) there is still an Object(...) construction function which takes key/value pairs (which then become "own properties"), and the { ... } syntax still constructs these prototype objects. This caught me by surprise (and I had to check AHK source code to verify), I expected { ... } to create Map objects just like [ ... ] creates Array objects. Plain objects that are neither Map nor Array still seem to accept obj.property (:= value) syntax for modifying or retrieving arbitrary fields, just like an associative array. Dynamic properties (i.e. get/set functions in disguise) are treated specially though (i.e. defined with DefineProp). I suspect this was probably kept in order to have a convenient mechanism to support class fields (both static and per-instance), however it feels kind of confusing/redundant and/or defeating the point of separating Array/Map into different types.
- There's a separate ClipboardAll type, however it looks to me like a holdover from before Buffer was introduced. I think ClipboardAll() should just return a Buffer.
- I know AHK objects implement IDispatch so they can be passed as COM objects. I can't remember where to find this in the documentation though. In general, COM marshalling rules are a bit hard to find information on.
- I really like the enhancements made to DllCall (including the new ComCall function) that integrate so much better with objects, and the new HRESULT handling, and how you can easily wrap stuff using bound functions. However, I don't like how ComObjCreate still returns a raw IUnknown pointer instead of a ComObject of type 0xD for non-IDispatch objects.
- ComObjError should probably not exist (i.e. it should always be enabled) since it makes writing robust wrappers slightly more cumbersome.
- NumPut's new mode is nice and with a bit of wrapping/bound-function magic it could be used to serialize structs in one go; however in practice it might not be as useful due to it not obeying struct packing rules (basically, all struct fields are aligned to the required alignment of their type, which is often just the size of the type itself). An alternative solution would be to use explicit padding fields in struct wrappers, but it might not be a great solution.
- I still think Options should not be the first parameter for GuiCreate or Gui.AddCtrl. Speaking of which, Gui.Add("Ctrl", ...) vs Gui.AddCtrl(...). One of these definitely has to go, and I'm not sure which.
- There's still three different types of Tab controls. There's got to be a way of bringing them together into a single one.
- Pipes/delimiters in Guis have to go in favour of real arrays.
- IsSet now exists. I'm not sure if this is already the case, but for optional function parameters accepting empty string as default, it would be useful if IsSet would return true if the parameter is not passed. This would make it trivial to replicate the same kind of non-constant-default-value behaviour observed in built-in functions (i.e. if !IsSet(param), param := GetSomeDynamicValue()).
- I think this is already planned anyway, but VarSetCapacity and the special variable-as-buffer modes of NumPut/Get/file ops definitely need to be removed. I think we're no longer in a position where we need to care about micromanaging variables' internal string buffer; and for binary data there's the Buffer object which is superior and more convenient in every way.