Forums

any all each

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

any all each

Postby Charles » Sat Jul 26, 2008 8:11 pm

Here are some ideas I'm playing around with, presented here for comment:

Python has any(seq) and all(seq) functions that return true if any element of the sequence is true, or if all elements of the sequence are true. A post at http://eikke.com/python-all-odity/ gives some examples and also points out that there is some performance overhead for this expressiveness.

In Cobra, I'm thinking about making this a language level feature:
Code: Select all
# form is:
# any <seq> ==> true or false
# all <seq> ==> true or false

print any for i in numbers get i > 100
assert all for i in numbers get i > 0 and i < 100


Because this is a language feature, the overhead discovered in that blog post can probably be eliminated by the compiler.

On a related note, I sometimes think that "first <seq>" and "last <seq>" would be interesting/useful. If so, might as well throw in a LISP-like "rest <seq>" (AKA "cdr").

I'm also contemplating an each keyword for convenience. Many times I find myself looping through a sequence just to invoke a method or collect the return values thereof, without any interest in the "loop control variable". Examples:
Code: Select all
# form is:
# each <seq>.messages ==> <seq of .messages return values>
# can be statement or expression

each shapes.display
each customers.billIfNeeded
names = each names.trim

# 'each' causes Cobra to break off the method invocation and apply it to each element.
# It's a short cut for:
for cust in customers, cust.billIfNeeded
names = for name in names get name.trim

# Like 'for expressions', 'each expressions' could have a 'where':
names = each names.trim where .trim.length

While 'each' is only a convenient syntax, I really like its succinctness. When I read "each customers.billIfNeeded" there is no decoding of intent or repeating of a var name. There's just less stuff to read and write.

Btw when I say "sequence" or "seq" I really mean anything IEnumerable including lists, arrays and any class that implements the IEnumerable interface such as Dictionary.keys or classes you write yourself.

Feedback is welcome.

-Chuck
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: any all each

Postby Kurper » Sat Jul 26, 2008 9:54 pm

Chuck wrote:...
names = each names.trim
...

I like the idea of this, but it seems like it'd be ambiguous if you tried to do something like:
Code: Select all
names = each names.trim.reverse
# or, the other way around:
names = each nametypes.names.trim

Not allowing chaining like that or requiring parentheses would work, but either would be kind of icky.


Off the top of my head, something like:
Code: Select all
names = names.each.trim

or:
Code: Select all
names = names.each(trim)

would be more appealing grammatically and ambiguity-ly, but they have problems as well.

Code: Select all
names = names.each(.trim)

would also be nice, if the vanilla dot weren't already meaningful.

In the "completely crazy" camp, there's also the option of assuming people use meaningful plural variable names and automagically stripping off the trailing "s", letting you do something like:
Code: Select all
names = names.each(name.trim)

but I'm sure that would cause thousands of problems.
Kurper
 
Posts: 6

Re: any all each

Postby relez » Sun Jul 27, 2008 3:46 am

that idea is very nice but it seems, at a first look, that Kruper is right in sense that some ambiguity can rise.... time to check, for me
relez
 
Posts: 69

Re: any all each

Postby Charles » Sun Jul 27, 2008 2:59 pm

(Btw I edited my original post to pluralize 'shapes' and 'customers' as I had originally intended.)

My intention was that "each foo.bar.baz" would execute ".bar.baz" on the elements of foo. But okay, let's take the example of:
Code: Select all
each .store.customers.billIfNeeded

Does Cobra use typing to "stop" at the first sequence (.customers), or would you be required to say:
Code: Select all
custs = .store.customers
each custs.billIfNeeded

# or
each (.store.customers).billIfNeeded

# or, speculating here, 'each' takes two args separated by a space:
each .store.customers .billIfNeeded

# in which case it could be a binary operator:
.store.customers each .billIfNeeded
.shapes each .display
names = .names each .trim.toLower

"reverse" could be a unary operator like "any" and "all". Note that keywords in Cobra only block out their usage as names for arguments and locals. You can still use them as method and property names. Type names are capped so no conflict there.

Btw the history of this is related to Comega which has "streams" that are denoted by SomeType*. When you invoke a method on a stream, you are actually invoking that method on the elements of the stream. In .NET, a stream would be IEnumerable<of SomeType>. I briefly contemplated allowing:
Code: Select all
def foo(custs as Customer*)
    custs*.billIfNeeded  # * indicates "on each element"

But the form "foo*.bar" is ambiguous with "foo MULTIPLY .bar" And the form "customers.billIfNeeded" is ambiguous for user defined collections that implement IEnumerable<of T>, but have interesting methods to invoke on the collection itself.

Finally, one more idea would be to choose a different symbol to avoid the collision with MULTIPLY:
Code: Select all
def foo(custs as Customer@)
    custs@billIfNeeded

def foo(custs as Customer:)
    custs:billIfNeeded

def foo(custs as Customer..)
    custs..billIfNeeded


I kind of like the double dot. It indicates "more than one" in the type decl and in the method invocation it indicates that you're going deeper than invoking the method on the collection itself. Whichever symbol was chosen, it's make the precedence of something like "any custs..hasBalance" obvious.

Hope you guys don't mind all this out loud speculation.

Again, feedback is welcome.

-Chuck
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: any all each

Postby hopscc » Mon Jul 28, 2008 4:59 am

any|all <seq> No real opinion - what problem/situation are these constructs being introduced to solve?

first/last/rest - these are just syntactic sugar yes ?
first = <seq>[0],
last = <seq>[<seq>.(length|count) -1] ( seq[[-1]] (:-) )
rest = <seq>[1:]

each - yeah sure might be convenient at times
explicit is better than implicit.
Make the args separate and imperative

Code: Select all
#each <seq>  <.itemMethodName> (...)
 each mailList.names  .billIfNeeded      # simple statement form
 trimmedMailLIst = each mailList.names .trim   # single invocation expression
 each mailList.names  .trim .tolower .removeDup .printInvoice # processing chain

first arg is seq to apply to, following args are methods to invoke on the sequence (items) in order

I think making it into an operator obscures the action being done - ('eaching'') readabilitywise - using punctuation for the operator obscures it even more
( of the ones given tho '..' is probably the better choice)

One advantage of the keyword oriented syntax is that it leaves open and gives a cleaner appearing syntax for the possibility of using it for arbitrary functions when/if available ( instead of just item supported methods) and lambda expressions ( or possibly Static methods that take a <seq> as the only arg)
e.g.
Code: Select all
fn addDot(s as String) as String
   return s + '.'

each methodNames addDot

each methodNames lambda(s as String) : return  s +'.'

class Utils
    def addDot( s as String) as String is Shared
        return s+'.'
...
dottedMethods = each methodNames Utils.addDot
hopscc
 
Posts: 632
Location: New Plymouth, Taranaki, New Zealand

Re: any all each

Postby Charles » Tue Jul 29, 2008 4:02 am

any|all are for expressiveness. You could survive without them with something like:
if Utils.allTrue(for i in numbers get i > 100), ...

# or if you added an extension method:
if (for i in numbers get i > 100).all, .doSomething

# but with the operator you get:
if all for i in numbers get i > 100, .doSomething

# and I'm tempted to allow collapsing of "all for" to "all" and "any for" to "any":
if all i in numbers get i > 100, .doSomething

# could we collapse further?
if all numbers > 100, .doSomething

if all names..trim.length > 0, .doSomething

The last few examples are neat, but such ideas deserve to be sat on for awhile as we might realize later they are undesirable or outright problematic.

This expressiveness not only applies to "if" and "while", but also contracts which are boolean conditions.

I agree that first/last/rest/reversed/sorted are also conveniences and potentially more trivial than all/any. I'm not as sold on these and they're highly speculative.

I feel strongly that if named methods are defined with "def name(arg1, arg2)" then anonymous methods should be defined with "def(arg1, arg2)" ... in other words, the same format, but without the name! Examples:
customers.sort(def(a as Customer, b as Customer))
return a.name.compareTo(b.name)

customers.sort(def(a as Customer, b as Customer)=a.name.compareTo(b.name))

# and with type inference a la C# anon methods and lambdas:

customers.sort(def(a, b))
return a.name.compareTo(b.name)

customers.sort(def(a, b)=a.name.compareTo(b.name))

One form provides the code in an indented block of statements, just like defining a regular method. The other form defines the anonymous method with =<expr> (and is essentially a lambda).

Regarding "each" with a "lambda" (or inline "def"), there is already the for expression for this which gives you an explicit "control" variable to use in arbitrary expressions:
names = for name in names get .addDot(name)
names = for name in names get name + '.'

# which is close to the statement version in keywords and order:
newNames = []
for name in names
newNames.add(name + '.')
names = newNames

# but obviously much more convenient!


In your "each mailList.names ..." example, I don't think .removeDup can be in there if what you meant was for the sequence to remove duplicates. Preceding that is .trim and .toLower which work on the strings. Is there any advantage to separating the message chain by spaces? Since they are not in a for-expression, I think they would not be in an "each":
names = for name in .names get name.toLower.trim
names = each .names .toLower.trim

# and then should "each" come in between like all other binary operators?
names = .names each .toLower.trim
.store.customers each .billIfNeeded

# I suppose it then becomes tempting to go the Smalltalk/Ruby route and allow the each to take a code block:
.store.customers each cust
print 'Billing', cust.name
cust.billIfNeeded

But do we really want 2 forms for looping? What's the rule for determining if the expression after each is an rvalue result or an lvalue variable to set?

Also, I would like some abbreviated form of "IEnumerable<of Shape>" to encourage more use of this type for arguments and return values (it's often more appropriate than List<of> or even IList<of>). Additionally, it would make Cobra code less .NET centric for when Cobra runs on top of JVM, Objective-C, Android, etc. That's where the .. idea was coming into play. I suppose we could in fact go with * which is common in regexes, db speak and UML (I think):
def display(shapes as Shape*)
.suspendUpdates
try
shapes each .display
finally
.resumeUpdates


Food for thought,

-Chuck
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: any all each

Postby jonathandavid » Wed Jan 14, 2009 2:13 am

Sorry to revive this old thread.

Chuck, I was curious about what your final decision was with regard to the "each" keyword/operator.

If I may offer my 2 cents, my vote goes for:

names = names each .trim.toUpper


One more thing: have you considered adding a "where" clause to this "each" construct?

customers each .billIfNeeded where .age > 18


Finally, the .method syntax is very convenient, but what happens if I want to apply an operator on the iterated items instead of calling a method? For instance, I might have a sequence "numbers" of ints, and wish to add 3 to each. Could the following syntax be made to work?

numbers each += 3


Another example: get a new sequence where each positive item is multiplied by two:

numbers = numbers each * 2 where > 0


This is unlikely to be feasible from a grammatical point of view, but you'll agree that it would be cool. If we stretch things even further, we reach something that simply cannot be done:

squared = numbers each ?????


Here I would like to multiply each item by itself, but I can't because I have no way to refer to the current item. A solution to this would be to provide a special keyword that refers to the current item inside an each expression. I suggest "item":

numbers each item += 3
numbers = numbers each item * 2 where item > 0
squared = numbers each item * item


When only method invocation is needed, "item" could be optional. This way, the following lines would be equivalent:

names = names each .trim.toUpper
names = names each item.trim.toUpper
jonathandavid
 
Posts: 159

Re: any all each

Postby Charles » Wed Feb 04, 2009 7:11 am

So I went through these same thoughts that you're having and came full circle back to the for expression:
for c in customers get c.balance
numbers = for n in numbers where n > 0 get n * 2

"for" supports enumeration, filtering and transformation.
See http://cobra-language.com/docs/manual/expressions/for.html.

Going down the "each" path just seemed to open up a lot complexity and having too many ways to do things. So I settled for sticking with for and using a single character variable (or often two char for compound class names like "ci" for CustomerInvoice).
Charles
 
Posts: 2515
Location: Los Angeles, CA

Re: any all each

Postby jonathandavid » Wed Feb 04, 2009 9:56 am

Chuck wrote:So I went through these same thoughts that you're having and came full circle back to the for expression:
for c in customers get c.balance
numbers = for n in numbers where n > 0 get n * 2

"for" supports enumeration, filtering and transformation.
See http://cobra-language.com/docs/manual/expressions/for.html.

Going down the "each" path just seemed to open up a lot complexity and having too many ways to do things. So I settled for sticking with for and using a single character variable (or often two char for compound class names like "ci" for CustomerInvoice).



I concur, "each" and "for" had too similar goals and could end up confusing people. On a related note, considering that generators and "list comprehensions" are already supported, would it be too hard to implement generator expressions, à la Python?

names = List<of String>(for p in persons <span style="font-weight: bold">yield </span>p.name where p.age > 18)


This example would have worked as well with a regular list comprehensions, but I'm sure there are others in which a generator comprehension would make a difference in terms of performance / memory usage (typically when elements are consumed one by one, for example in a function that computes the sum of the elements in a sequence).
jonathandavid
 
Posts: 159

Re: any all each

Postby Charles » Wed Feb 04, 2009 3:40 pm

My current thinking is that a generator form would come via LINQ which kicks off with "from":
names = from p in persons where p.age > 18 yield p.name

# in C# that's
var names = from p in persons where p.age > 18 select p.name;

I'm glad you brought it up though because your example gives the idea that "yield" is a better keyword than "select" since there is already a "yield" keyword and using it is informative. So maybe we could kick off the query with "for" anyway as you suggested.
Charles
 
Posts: 2515
Location: Los Angeles, CA

Next

Return to Discussion

Who is online

Users browsing this forum: No registered users and 67 guests