Forums

Cobra design points [was: Partial Application]

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

Cobra design points [was: Partial Application]

Postby jaegs » Sun Jul 08, 2012 4:00 pm

It would be cool if partial application (not to be confused with currying) was built into the Cobra Language. In Python there are two options:
Code: Select all
def sum( a, b, c ):
  return a + b + c

#option 1
sum5 = lambda b, c: sum( 5, b, c )
sum5( 10, 11 )

#option 2
from functools import partial
sum6 = partial( sum, a = 6  )
sum6( b = 7,  c = 8 )

which in both cases is more annoying to code than useful.

Scala seems to do a much better job with underscore wild cards:
Code: Select all
def sum( a: Int, b:Int, c:Int) = a + b + c
val sum6 = sum(6, _:Int,  _:Int)
sum6(5,7)

Though there is something weird going on such that the types need to be respecified. I think it's because the underscore notation is syntactic sugar for a lambda.

Ideally the Cobra code would look like this:
class Stuff
def sum( a as int, b as int, c as int) as int
return a + b + c
def main
sum6 = .sum(6, _, _)
sum6(5,7)


The underscore would have to be disambiguated from a line continuation.
jaegs
 
Posts: 58

Re: Partial Application

Postby jaegs » Mon Jul 09, 2012 5:51 pm

Some other thoughts.

  • If you ever get Tuples implemented, then a method could have multiple return values and the out keyword could be axed. out always confuses me since its an input but really an output.
    dict = {:}
    value as Object?
    found = dict.tryGetValue( "key", out value)

    Existing .NET functions such as tryGetValue could be automagically be turned into
    found, value = dict.tryGetValue("key")

    Where the return value of dict.tryGetValue is Tuple<of bool, of Object?>
    EDIT: I found this idea mentioned on a previous forum post, it was just hard searching for the word "out"

  • In lambdas, are the parentesis following the "do" keyword really necessary?
    t.sort( do(a as int, b as int) = a.compareTo(b) )

    could be
    t.sort(do a as int, b as int = a.compareTo(b) )

    The parameters are already bordered by a "do" and "=." Lambdas are intended to be a shorthand, so I believe the syntax should reflect that.

  • Similarly, why is the "of" keyword required in generics?
    List<of int>

    It seems obvious to me that whatever is inside the angle brackets is a type and no other decoration is necessary. Plus, I imagine really long generics could get messy (well, messier than they already are)
    IEnumerable<of List<of Dictionary<of string, of int>>>


  • One place that Java shines over C# is enums
    Java programming language enum types are much more powerful than their counterparts in other languages. The enum declaration defines a class (called an enum type). The enum class body can include methods and other fields. The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared. This method is commonly used in combination with the for-each construct to iterate over the values of an enum type.
    Link
    Essentially, they replace having to type "public static final className" over and over again. I think the only catch is that they can't be OR'ed together bit flag style like C#'s integral enums. Java has an EnumSet instead. Here are some cool Java enum tricks. I think Java style enums would work well in Cobra.

  • Named, default, and / or optional parameters... any plans?

  • I was surprised to see that
    a,b = [1,2,3]

    doesn't throw a compile time exception and immediately thought this must be a language error given that Python does throw. However, I see know that there is a debate on the topic, so I here's my opinion:
    Suppose that tuples have been implemented in Cobra. Each element of a tuple is included because the programmer needs it. This
    a,b = (1,2,3)
    (parens denote a tuple) is probably an error - the programmer forgot the last element and an exception should be thrown. As a statically typed language, this is also a contradiction - the types of the LHS don't match the types of the RHS. For consistency, lists and arrays should follow the same rule.

    I also agree with Charles that array slicing is a better way to get the first few elements from a list since it more clearly shows the programmer's intentions.

    Finally, the Cobra list notation is such a Python clone that I think this minor, silent difference will have unexpected consequences to those with a Python background.
jaegs
 
Posts: 58

Re: Partial Application

Postby Charles » Mon Jul 09, 2012 11:06 pm

Should I rename this thread from "Partial Application" to something else?

Here are my responses:

Partial application

The "option 1" using lambda is just really defining a function that takes two args and calls out to another function. There is nothing special about it afaict:
Code: Select all
>>> lam = lambda x: x
>>> lam
<function <lambda> at 0x3bbd30>

In any case, although I understand what partial applications give you, I don't find myself desiring them in day to day coding. However, sometimes that's misleading because if something is not available, you will tend to stop thinking about it in the practical interest of getting your work done. What I'm leading up to is this: Can you provide practical examples where partials make code cleaner?

Also, were you expecting to be able to pass a reference to the partial around like you can with a method?

Tuples

More and more I'm shying away from tuples > 2. Seems like I always regret using them in Python. For example, I might have a method that returns (a, b, c, d). One problem that can occur is a realization that the order is not logical and should be switched to (a, c, b, d). However, doing so will break every place the method is used, which is a deterrent. Another problem is adding info such as providing an 'e' after the 'd' as in (a, c, b, d, e). Although I suppose Cobra's current approach means the call sites will not be broken. However, it also means the call sites will not have the info like they would if you instead returned a dictionary or object and then examined it with `trace` or a debugger. Another minor problem is that tuple usage sometimes ends up encouraging code like this:
Code: Select all
t = obj.foo
.bar(t[6])
# what is element 6?

I did add Pair<of TA, TB> to the Cobra std library recently. And it's only a matter of time until I add support for unpacking that.

My current belief is that if you want to return more than two values, then you should use a dictionary or an object which will avoid the problems described above.

Regarding the awkwardness of some of the .NET API (most of which is nice), we have several extension methods to the .NET class library including "dict.get(key, default)" which I enjoy over the awkward .tryGetValue. See:
StandardLibraryExtensionMethods
/cobra/trunk/Source/Cobra.Lang

Lambda Syntax

The "do" keyword is also used for anonymous methods/closures. If there is not "=" after "do(a, b)" then there must be code for the anonymous method indented on the next line. Combined with the fact that commas are used to separate arguments to methods--the lambda could be inside a method call that takes more than one argument--I'm doubtful we can drop them.

However, I have been playing around with the idea that if an expression contained tilde-marked variables, it would be considered a lambda:
t.sort(~a.compareTo(~b))

Note that this approach not only eliminates the "do" and the parens, but it also means you don't have to declare the variable twice (once as an argument and once when using it). There is the issue of what to do if you want to refer to the args out of order, such as reversing the comparison. Putting aside the fact that in this specific example, you could multiply by -1, I was thinking an extra tilde like so:
t.sort(~~b.compareTo(~a))

We also need inference of types on lambdas if the above is going to work. I have a local workspace that technically does this (the inference not the tildes), but unfortunately it reveals problems with method overload resolution, so it's not ready to be checked in yet.

Getting back to "~a" for lambdas, I'm strongly in favor of it until someone talks me out of it. I think it's easy to spot and makes the syntax cleaner.

"of" in generics

I will start a different thread for this.

Java enums

Yes, Java enums do look pretty sweet. I haven't thought much about how to implement them in Cobra while still outputting something that is a true .NET enum. I guess you could say it's a low priority.

Named and option params

Definitely in the future, but probably behind things like:
-- Pick up extension methods declared in C#.
-- Support lambda expressions being interpreted as expression trees.

These issues come up when trying to leverage various recent .NET libraries.

Multi-assignment

I'm still in favor of the Python approach of complaining that there are too many values to unpack. A slice can be used when needed.

Other things

I've started a programming koans project with Llewellyn Falco which I'll push out publicly later this month.

I'm looking to push out a Cobra 0.9 release soon which requires a bit more compiler work and some web site updates. Todd A has started on a new color theme.

<"IDE support" was here> - making a new thread
Charles
 
Posts: 2510
Location: Los Angeles, CA

Re: Partial Application

Postby jaegs » Tue Jul 10, 2012 8:08 pm

Cool! Thanks for the in depth response. Haha, I guess the title should be changed.

Partial Application
Consider sorting. Partial application can be used to create new sorting functions on the fly based on different keys.
Code: Select all
sorted("This is a test string".split()) #['This', 'a', 'is', 'string', 'test']

sortedByLower = partial(sorted, key = str.lower)
sortedByLower( "This is a test string".split() ) # ['a', 'is', 'string', 'test', 'This']

sorted( [(1,2), (5,1), (0,19)] ) #[(0, 19), (1, 2), (5, 1)]

sortedBySecondElement = partial(sorted, key = lambda e: e[1])
sortedBySecondElement( [(1,2), (5,1), (0,19)] ) #[(5, 1), (1, 2), (0, 19)]


Tuples
I see what you mean, tuples can definitely be abused. I suppose Python needs long addressable tuples since they are more efficient than lists when storing immutable (usually homogeneously typed) data. Cobra as arrays for a similar purpose. Nevertheless, language support for unpackable couples and triples would be nice. Quadruples start to get confusing. It seems relevant to mention that OCaml does not support accessing tuples by position as a matter of principle (I think) since implementation-wise it probably could.

Lambda Syntax
I'm a fan of using tildes on input variables but not so much the double tilde, even though it kind of looks like a snake. If there are three inputs, are 3 tildes required? How would you express parameterless lambdas? Perhaps if order of arguments matters, the user would have to opt to write a closure. Alternatively, the default parameter ordering could be alphabetical, based on the variable names. Maybe that's weird too.

Enums
I'm not totally sure why you would want to implement Java style enums as integral C# enums. At heart, Java enums are a syntactic shortcut for public static final fields encapsulated within a static class. Other than using the keyword readonly instead of final, I believe .NET & C# are capable of implementing class style enums if desired. For compatibility reasons, I suppose access to existing C# integral enums could continue to use the same syntax. Anyway, I figured this seemed like an important pre-1.0 decision since it would be difficult to change later on.

One final thing:
*args and **kwargs
Together probably both the most powerful and confusing feature in Python. I don't know how many times I've relied upon zip(*lst) when dealing with 2-dim lists. Makes me wonder if a better syntactic implementation is possible.

Looking forward to the .9 release.
jaegs
 
Posts: 58

Re: Cobra design points [was: Partial Application]

Postby Charles » Wed Jul 11, 2012 11:24 am

Lambda syntax

Just to be clear, in my proposal, the double tildle (~~) is only needed when referring to the args out of order. The majority of lambda expressions I see in Python and C# either take 1 arg, or they take 2 args and can be used in the same order they are declared. So most of the time, one tilde would be sufficient.

I wasn't sure if that came through in my previous explanation.

Enums

Cobra has "two way compatibility with .NET" meaning that in addition to (1) consuming .NET libraries you can also (2) vend out .NET libraries written with Cobra. This comes up in other places. For example, Cobra's mix-in feature is not found in C#, but Cobra outputs both an interface and a class for the mix-in and uses the interface as the type. In this way, C# users can consume a Cobra library even if it uses mix-ins, although the C# programmer won't be able to mix-in "code injection"--they'll see an interface only just like if they had created the library in C#. So if Cobra adopted Java-style enums, it would need to do something similar.

args

What was it that you found confusing about Python's *args and **kwargs?
Charles
 
Posts: 2510
Location: Los Angeles, CA

Re: Cobra design points [was: Partial Application]

Postby jaegs » Wed Jul 11, 2012 5:48 pm

Lambda Syntax
Yeah I guess three arg lambda's are rare and probably better off coded as closures. On the other hand, 0 arg lambdas (aka "thunks") are, in my experience, fairly common. Example areas where they show up are in parallel program and lazy evaluation. True, thunks aren't needed much in Python because iterators accomplish laziness and the GIL means that there are no threads. However, one way to implement a thread pool or MapReduce in C#, is to have a function with a thunk as an argument.
Code: Select all
ThreadPool.RunTask( () => Foo( arg1, arg2, arg3) );

Here's a different, unrelated, extremely obscure .NET example
Code: Select all
WorkflowServiceHost workflowServiceHost = serviceHostBase as WorkflowServiceHost;
if (null != workflowServiceHost)
{
  string workflowDisplayName = workflowServiceHost.Activity.DisplayName;
  TrackingProfile trackingProfile = GetProfile(this.profileName, workflowDisplayName);
  workflowServiceHost.WorkflowExtensions.Add(()  =>
    new EtwTrackingParticipant  { TrackingProfile = trackingProfile } );
 }


args
In my opinion, *args and **kwargs are poorly named. The asterisks syntax also doesn't give any clue to their purpose. The confusion on stackoverflow confirms that I'm not alone.
jaegs
 
Posts: 58

Re: Partial Application

Postby torial » Wed Jul 11, 2012 8:22 pm

Charles wrote:Lambda Syntax

The "do" keyword is also used for anonymous methods/closures. If there is not "=" after "do(a, b)" then there must be code for the anonymous method indented on the next line. Combined with the fact that commas are used to separate arguments to methods--the lambda could be inside a method call that takes more than one argument--I'm doubtful we can drop them.

However, I have been playing around with the idea that if an expression contained tilde-marked variables, it would be considered a lambda:
t.sort(~a.compareTo(~b))

Note that this approach not only eliminates the "do" and the parens, but it also means you don't have to declare the variable twice (once as an argument and once when using it). There is the issue of what to do if you want to refer to the args out of order, such as reversing the comparison. Putting aside the fact that in this specific example, you could multiply by -1, I was thinking an extra tilde like so:
t.sort(~~b.compareTo(~a))

We also need inference of types on lambdas if the above is going to work. I have a local workspace that technically does this (the inference not the tildes), but unfortunately it reveals problems with method overload resolution, so it's not ready to be checked in yet.

Getting back to "~a" for lambdas, I'm strongly in favor of it until someone talks me out of it. I think it's easy to spot and makes the syntax cleaner.



I really like this idea. Very compact, and doesn't needlessly clutter with redundant information.
torial
 
Posts: 227
Location: IA

Re: Cobra design points [was: Partial Application]

Postby Charles » Wed Jul 11, 2012 8:40 pm

You're correct that thunks come up often enough. They are currently supported like so:
# lambda style; only an expression
ThreadPool.runTask(do=.foo(arg1, arg2, arg3))
# or closure style; as many statements as you like
ThreadPool.runTask(do)
.foo(arg1, arg2, arg3)

I guess you're getting at the inconsistency that n-ary lambdas of n > 0 will use tildes (like "~a") while thunks will/must use "do". Perhaps the resolution is to always require the "do":
t.sort(do=~a.compareTo(~b))

This restores the syntactic rule that lambdas and closures always start off with the same keyword, something we currently do with declarations (class, interface, def, get, var, etc.) and method references (ref).

Given that "do" is a keyword, I wonder if we could drop the equal sign regardless of whether it's the explicit syntax or the tilde syntax that we're talking about:
t.sort(do(a as int, b as int) a.compareTo(b))  # current, explicit syntax

t.sort(do(a, b) a.compareTo(b)) # arg type inference in the future

t.sort(do ~a.compareTo(~b)) # idea

Hmm, I've seen the "=" with the "do" so much that the above looks unnatural to me except in the last case.

Thoughts?
Charles
 
Posts: 2510
Location: Los Angeles, CA

Re: Cobra design points [was: Partial Application]

Postby torial » Wed Jul 11, 2012 8:48 pm

Charles wrote:Given that "do" is a keyword, I wonder if we could drop the equal sign regardless of whether it's the explicit syntax or the tilde syntax that we're talking about:
t.sort(do(a as int, b as int) a.compareTo(b))  # current, explicit syntax

t.sort(do(a, b) a.compareTo(b)) # arg type inference in the future

t.sort(do ~a.compareTo(~b)) # idea

Hmm, I've seen the "=" with the "do" so much that the above looks unnatural to me except in the last case.

Thoughts?

Agreed -- only the 3rd option seems the most natural of the bunch.
torial
 
Posts: 227
Location: IA

Re: Cobra design points [was: Partial Application]

Postby jaegs » Wed Jul 11, 2012 9:02 pm

3rd but maybe also 2nd if the args are used out of order (instead of '~~').
jaegs
 
Posts: 58

Next

Return to Discussion

Who is online

Users browsing this forum: No registered users and 3 guests

cron