Forums

More design points

General discussion about Cobra. Releases and general news will also be posted here.
Feel free to ask questions or just say "Hello".

More design points

Postby jaegs » Sat Jul 21, 2012 6:35 pm

Here's some things I've been thinking about while working on the MultiList class.

First, lists and arrays

Code: Select all
print [0,1,2,3].getType #System.Collections.Generic.List`1[System.Int32]
print (for i in 4 get i).getType #System.Collections.Generic.List`1[System.Int32]   
print int[].getType #System.Int32[]
print @[0,1,2,3].getType #System.Int32[]


It seems to me that line 3 should be a List. This would match the array literal syntax of line 1.

I find line 2 tricky for two reasons. First, the "get" keyword has a different meaning when dealing with properties. Second, there is nothing about the "for-get" syntax that indicates that a list is returned (as opposed to stream perhaps).

Finally, the compiler replaces generics angle brackets with square ones, and so generic types start to look like arrays.

First I propose that line 3 be a list and that int@[] be for arrays, to match the literal syntax. The compiler should also output angle brackets for generics

I think list comprehension should be written as follows
lst = [ for i in 4 yield i ]

This would be equivalent to making a stream comprehension and evaluating it to a list.
lst = (for i in 4 yield i).toList

Requiring square brackets around a list literal also means that braces { } could be used to specify a set or dictionary comprehension, @[ ] could be used for arrays, and maybe '[ ]' could be a StringBuilder comprehension.

for loops

for i in foo
pass


It's unclear whether 'foo' is an int or stream.
for i in 0:foo
pass


Requiring '0:' for ints is more explicit. Perhaps the following could be used as shorthand.
for i in :foo
pass


Streams
Do Cobra streams have a 'next' and 'current' method? I want to merge two streams into a stream of pairs. As a platform dependent workaround, I casted each of the streams to C#'s IEnumerable.

Vari
Given a method with parameter 'as vari T', is there a way to call it with an 'IList<T>' variable?

cue init
Is there a way to make a private constructor? I tried 'cue _init' but that didn't work.

require
init allows multiline requires but methods and properties do not. When I tried to write a multiline require block in a method, I got the following confusing error
#Expecting DEDENT, but got "for" instead.

To get around the restriction, I wrote
require _
data.count >0 _
and data.count + start <= _shape.count _
and not _isReadonly _
and not _isPermuted

Also, I find underscore to be a weird line continuation character because it's also a character for variables names and such. What about a backslash?

indexing
From within a class
this[element] #doesn't compile

thisObj = this
thisObj[element] #works but gives
#warning: An explicit "this" literal is unnecessary for accessing members of the current object. You can remove "this".


Also
Code: Select all
x = [1,2,3,4,5]
print x[:-1] #[1,2,3,4]
print x[-1]
#Unhandled Exception: System.ArgumentOutOfRangeException: Argument is out of range.
#Parameter name: index
#  at System.Collections.Generic.List`1[System.Int32].get_Item (Int32 index) [0x00000] in <filename unknown>:0
#  at Test.Main () [0x00000] in <filename unknown>:0
#[ERROR] FATAL UNHANDLED EXCEPTION: System.ArgumentOutOfRangeException: Argument is out of range.
#Parameter name: index
#  at System.Collections.Generic.List`1[System.Int32].get_Item (Int32 index) [0x00000] in <filename unknown>:0
#  at Test.Main () [0x00000] in <filename unknown>:0


Error reporting
Cobra has the best compiler errors. To be even clearer:
"Test.cobra(5,21): error: Expecting GET, but got "yield" instead."
could say
"Test.cobra( line 5, column 21): error: ......"
jaegs
 
Posts: 58

Re: More design points

Postby kirai84 » Sat Jul 21, 2012 8:20 pm

Note on line continuation. It would be nice if a continuation character could be omitted at all when the line ends with an operator (including "." and ",") or the next line starts with one and has a proper indent. Like this:
require
data.count > 0
and data.count + start <= _shape.count
and not _isReadonly
and not _isPermuted

Easy to read and easy to parse.
Too bad it won't work well with "List <of Some>" and "aStream*" constructions.

Also underscore is used as continuation symbol in VB(.NET) so it's not that unconvenient.
kirai84
 
Posts: 24

Re: More design points

Postby Charles » Sat Jul 21, 2012 11:27 pm

Arrays

You may wish to use "trace" instead of print when investigating things. Note that "int[]" is an array type decl in Cobra. "trace int[]" outputs:
Code: Select all
    trace : int[]=System.Int32[] (MonoType)
          - in X.main

...which is correct. Cobra supports both language-based arrays and library-based lists (as do C# and Java, and their respective platforms/VMs).

For Expressions

For-expressions are essentially list comprehensions. I agree that this has to be learned since it cannot be immediately inferred from the syntax, but that's true of many things. Even if it returned a stream, that couldn't be inferred from the syntax either.

Re the use of the "get" keyword, when you take a for-statement like this:
t = List<of int>()
for x in stuff
t.add(x*2)
# and turn it into an expression:
t = for x in stuff ___ x * 2
# there needs to be a separator.
# I don't see anything wrong with using 'get'.

I see later in your post that you already realize the need for separator and you're leading up to 'yield'. More on that below.

Run-time Type Names

Re "Finally, the compiler replaces generics angle brackets with square ones, and so generic types start to look like arrays." ... that's not the compiler, that's the .NET run-time/VM. Even when coding in C#, a generic which was "List<int>" in C# source code will come back as "System.Collections.Generic.List`1[System.Int32]" at run-time.

With a little work, some code could take such names and Cobra-ize them (System.Int32 --> int, Foo`1[...] --> Foo<of ...>, etc.).

Syntax for array types

"int@[]" is interesting, but it also sort of looks like an empty array literal "@[]" preceded by a type "int" which may be considered needed since it's empty. In other words, this looks as much like a literal to me as a type decl. I think "int[]" shall remain.

Note that square brackets are also used for indexing on arrays, lists, strings and anything that defines an indexer. They are also used in string interpolation. Recalling your earlier comment about 'get', I would point out that it's very difficult not to overload some symbols and/or keywords in a language with a rich feature set intended for easy reading and writing by humans.

When I was at Lang.NET 2008, another language author and I lamented about how we didn't have as many different bracket types as we'd like (parens, square, angle and curly only make for 4). :)

Syntax for list comprehenions

So is your proposal this?
stream = for i in 4 yield i * 2
list = [for i in 4 yield i * 2]
array = @[for i in 4 yield i * 2]
set_ = {for i in 4 yield i * 2}
# What would a dictionary comprehension look like?
dict = {for x in something yield foo, bar }

For StringBuilder, I'm skeptical. Quotes and brackets won't work as '[ is the start of a valid string literal in Cobra. Also when I use StringBuilders, I often need separators between elements which means using a .join method on a list of strings, or building the string with various statements. But I'm willing to see examples if you had specific scenarios in mind.

for loops

You say "It's unclear whether 'foo' is an int or stream" but why only those two choices? "foo" could be a List<of int> (which is not a stream) or a class you defined that implements .getEnumerator.

Also, it's usually clear from the expression or variable name:
for i in list.count
trace i, list\[i]
for customer in customers
customer.billIfNeeded

(Ignore that funny backslash. Either phpBB or pygments choked on formatting the original source and that's a hack to avoid it.)

Streams

Streams don't have 'next' and 'current' methods and I wasn't planning so far that they would, but you bring up an interesting case. Under the hood, streams use the actual .NET IEnumerable<of T> and JVM Iterable<of T> types which have different methods (.current & .moveNext vs. .hasNext & .next).

Comega was the inspiration for streams, but poking around their web pages, I don't see how they handle this case, but I didn't look too hard.

An alternative could be to provide something in the Cobra std lib and/or the language that meets the case you describe:
# more brainstorming

# language:
for a, b in streamA, streamB
trace a, b
for a, b, c in streamA, streamB, streamC
trace a, b, c

# library:
for a, b in streamA.zip(streamB)
trace a, b
for a, b, c in streamA.zip(streamB, streamC)
trace a, b

But for now, your workaround will have to suffice.

Vari args

Underlying var args in .NET is an array. So C# supports supplying var args directly (the normal case) or with an array. Cobra follows suit. Note that List has a .toArray method.

You can see this covered in the test cases under CobraWorkspace/Tests/120-clasess/954-vari-args-overload.cobra

Private initializers

class X
cue init is private
base.init


Multiple require conditions

I don't follow you here at all. Can you show me the code that gave you the compile-time error?

Here is a multi-condition requirement:
class X

def foo
require
data.count > 0
data.count + start <= .shape.count
not .isReadOnly
not .isPermuted
body
pass

Note that the use of 'and' is not required as semantically there is no difference: all requirements are essentially and'ed.

Btw it's bad form to to use protected members in contracts (_foo) as contracts are considered part of the public interface. I think Eiffel even makes it an error and Cobra will probably follow suit at least for public members.

Line continuation

I got _ as a line continuation from VB. I liked it's low profile. I never liked \ as a line continuation in bash etc. because I was always looking for the character to follow it (\n \t)--lots of C, C++ and Objective-C left me expecting this.

This is a very subjective area of course.

Indexing

You said "this[element]" does not compile, but I cannot reproduce. This works for me:
class X

get [i as int] as int
return i * 2

def main
assert this[4] == 8
trace this[4]

.NET and JVM classes don't support "foo[-1]" for getting the last element (or throwing an index exception if there is none). The Cobra compiler could intervene and provide a wrapper but then indexing would become slower everywhere, every time. Instead I chose to add an extension method called .get that supports this. See StandardLibraryExtensionMethods.

Error reporting

It doesn't take long to learn the line number format which is commonly used in all other languages. I think the status quo is fine here.

Implicit line continuation

@kirai84, this is in the plans.


.... of the points you brought up @jaegs, the list comprehensions were the most interesting and IMO still in need of more discussion. The others were questions or feedback for which I believe the status quo will remain, but feel free to bring up further points if needed.
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: More design points

Postby nerdzero » Sun Jul 22, 2012 7:56 am

Regarding multiple require conditions: It sounds like you forgot the "body" keyword. This tripped me up a few times when I was learning the language. It is a little counter-intuitive as a dedent seems like it should be a sufficient indicator of a change of context but I will admit that now that I am used to it, it is more aesthetically pleasing to have "body" in there and it makes things a little easier to visually parse.
nerdzero
 
Posts: 286
Location: Chicago, IL

Re: More design points

Postby jaegs » Sun Jul 22, 2012 10:42 am

Syntax for Array Types
An alternative is to let "Array<int>" refer to an array and "int[]" refer to a list. This introduces no new syntax, lets the literal syntax for a list "[1,2,3,4]" match the type name and gives lists the syntactic upper hand. By this I mean, lists are used more often in C# (link) so they should have the simpler syntax. This pattern is followed elsewhere in Cobra since "[1,2,3]" is shorter than "@[1,2,3]."

Syntax for List Comprehensions
Yes that is pretty much my proposal. I like this idea a lot, so I'm glad you think its interesting. I may have gone astray mentioning StringBuilder. Essentially, what I meant is that for strings, one can already do this:
class X
def stream as int*
for i in 5
yield i
def main
print 'A stream: [.stream]' # A stream [0, 1, 2, 3, 4]

So with the proposal
print 'A stream: [ for i in 5 yield i ]' # A stream [0, 1, 2, 3, 4]


Streams
The ability to loop over multiple enumerators would be really useful
for a, b in streamA, streamB
trace a, b


For example,
def equals(lst1 as IList<of int>, lst2 as IList<of int>) as bool
#this
for i in lst1.count
if lst1[i] <> lst2[i]
return false
return true

#could be replaced with this
for e1, e2 in lst1, lst2
if e1 <> e2
return false
return true

EDIT: Seems like the above syntax would conflict with commas already being used for one line "for" loops
for i in 5, i+=1


Note that if stream methods aren't supported, there are still some things that can't be done. For example, simultaneously looping over two streams of different sizes. Once the shorter stream finishes, it yields default values instead. Or moving only one position in stream A for every 5 positions in stream B.

Multiple require conditions
Thanks, looks like I forgot the "body" directive. I think the issue here is that the meaning of "DEDENT" is unclear.

Indexing
I can see how negative indexing could be bad. On the other hand, I imagine every programmer from Python will try to do so in Cobra. Perhaps an error message such as "Negative indexing is not supported, consider using .get instead" could help.

If coders start doing
lst = [1,2,3]
trace lst[-2:-1][0]

Things could get ugly.

Looks like my index error was because I was trying to use a list to call a vari index
this[lst]
#should be
this[(index to List<of int>).toArray]


Enums
I was thinking some more about class enums. If there isn't a way to make them fully compatible, then perhaps Cobra could support both integral enums, with a lowercase e, and class Enums with an uppercase E. There is benefit to having both types. Integral enums are lightweight, easily serializable, and backwards compatible. Class Enums are powerful and versatile and could still be accessed in C# like any normal class.
jaegs
 
Posts: 58


Return to Discussion

Who is online

Users browsing this forum: No registered users and 32 guests