Danielle Fong | 29 Jun 14:58

Hello ProjectFortress! Questions and discussion on scripting, pattern matching, goal-directed programming, and other nondeterministic idioms.

Hello Project Fortress!

I've been lurking around the project for a while now. As someone who's done a lot of scientific programming, I was routinely dismayed by the linguistic support for what I wanted to do, and set to work learning language design, sometime around last summer. I made a list of things I wanted to try, things I thought I'd really want when I got back to scientific programming. Happily, once I discovered Fortress, I found it had most of them, and many more times good ideas that I hadn't expected -- especially in parallelism. And so I am very excited.

I'm especially glad that you're using mathematical notation. There just weren't enough operators in other languages for everyone to do what they wanted. But the improvements in readability are enormous. And, we can't forget Kernighan's law:

"Debugging is twice as hard as writing the program, so if you write the program as cleverly as you can, by definition, you won't be clever enough to debug it."

That's a big problem people encounter in scientific code. It's not impossible to write, in current languages, but it's really, really hard to read, because all of the management of the mathematical objects (say, vectors and matrices and big numbers), all of the parallelism and numerical checks, needs to be handled inside the main code. And it mucks everything up. So improving notation, and parallelism, and language extensibility, and providing all sorts of powerful primitives and the ability to carry around properties, all this excites me.

I've been kicking a bunch of questions for a while now. And I wanted to put in some more study before I asked them. But I read the exhortation on the Fortress Bootcamp today, to not "hold back your impressions!" So I'll bite my lip and make a fool of myself: here are some of mine:

There are what seem like competing goals you're working under. HPC seems to demand things like unboxed variables, and static type checking, and rock solid exception handling, and a great security model.

On the other hand, I see great potential in Fortress, or a Fortress derived language, as a *scripting* language for scientists and mathematicians. I can imagine something like it replacing Mathematica as the default first language people learn, and the default language people work and think in. This is partly because the parser and character set are so powerful, and the language is so extensible, that the natural expression of many statements will, with the right libraries, take little effort to translate into Fortress. Also, with parallelism and easily parallelized constructs built so deeply into the language, I can imagine the computations that slow maple and mathematica down just blazing on Fortress, and that would be really great.

But for it to be a nice scripting language, the syntax seems a little heavy. At least at first, that's my impression. I look at the conjugate gradient example and think 'my, that's beautiful, it's like the canonical encoding of what that algorithm *means*'. But then I look at even Hello World and I'm a little daunted, because it still looks like there's an awful lot of code around that isn't core to the meaning. What's all this 'export' and 'executable' and run =, and component stuff? The canonical representation of hello world is 'print "hello world"'!

One of the core philosophies of the scripting world is that you can get up and running in an extremely short time. And then it's a really, really simple matter to move next to simple calculations, and variable assignment, and loops, and so on. Access to a powerful and helpful interactive interpreter/debugger, like Python, or Mathematica, is really helpful too (if you go one step further, and typeset code and equations, it will be even better).

These features make a smooth gradient for absolute beginners to climb. Eventually, they'll get all the way up to being able to build library code. I think that smoothing that gradient out could really be crucial for Fortress. A goal should be that it's a language people will want to teach students *first*. It is so different from previous languages that I see only a few of the old guard, those really in the know, switching -- the rest will have to pick it up early on. But that's okay, you have long term goals.

So one of my impressions, which I'll just put out here, is that having a 'script mode' might be a very handy thing: for code outside of a 'component' score, by default, assume that the component has its name being its filename, which by default exports (or even just plain runs) an executable. This simplifies things for beginners and everyone else -- you can just name components by the filename, you don't need to write the name twice, which was one of the things that annoyed me about Java, even before I started scripting. There's a name for this philosophy used in the Python world: don't repeat yourself.

For example, code blocks are delimited with 'end'. Are they needed? Python does without them, and I think that pays major dividends in readability. And it takes less work to type. Programmers by default seem to follow the path of least typing. Is there anything in the parser, or in the semantics, that need it? And I imagine that the default method for coding fortress would eventually include a realtime typesetting ide, so in fact readability would be even easier than in Python -- you could control the block spacing to guide the eye.

Finally, I would like to make the case for some exploration of declarative, nondeterministic programming. Fortress indulges in both, in some sense, due to implicit parallelism. I don't really have anything settled in my mind yet, but there are directions that are interesting to me. I have been playing with the pattern matching mechanisms of Erlang and Haskell (of which multiple assignment, like python and ruby have, is a subset). It's not a fully declarative language like Prolog -- they used only the stuff they could make fast. But what I've found is that it's extremely useful as a 'do everything' control flow abstraction. I'm afraid it's too late tonight for me to give examples, but if you ask me I can write some later. For now, you should know that Erlang uses pattern matching for variable assignment. Erlang uses it for sanity checks. Erlang uses it for string manipulation. Erlang uses it to simplify calling and writing anonymous functions. Erlang uses to it implement typing. Erlang uses it to implement array/list/data structure slicing. Erlang uses it to implement object orientation. Erlang uses it to streamline exception handling. Erlang actually has an if statement, but nobody uses it because case statements, with the limited pattern matching that Erlang gives, are so incredibly useful.

In principle pattern matching is the sort of thing I could add on later. But some of the biggest wins come from applying it in these nooks and crannies, and they're really everywhere. It could be a very valuable thing to have right at the core of the language. So I'd like to see,at this early stage, if it's a feasible thing to look into. Erlang's pattern matching is one of the most powerful, time-saving abstractions I've come across, and they're not even taking it as far as they could.

The other thing is that the pattern matching is an abstraction that mathematicians are really used to. People have an innate sense of the 'shape' or 'pattern' of some expression, and can easily see you taking one thing, and splitting it up, and putting it in some other thing. It's very easy to grasp, conceptually. That's almost what mathematical calculation is: everytime you see a derivation with an = or an arrow what you're really seeing is some pattern matching and symbol manipulation, of which variable assignment is a tiny subcase. Even consider the way people write case statements, or multi valued functions: here, for example. http://mathworld.wolfram.com/HeavisideStepFunction.html

Exception handling is another thing I'm interested in. Unfortunately, I think the standard way of handling exceptions causes an awful lot of clutter.

Here are some examples.

Very, very often, you know that something could cause an exception, but you want that exception to pass silently, because it's either not important to the program, or it's part of a chain of actions for which you know what to do if any of them fail.

For example (in python, taken from http://code.causes.com/blog/drying-out-deep-checks):

We found ourselves very often writing code with conditions that looked like:

if object && object.child && object.child.valid?
do_something(object.child)
end

In this case all we really want to do is verify that object.child.valid? returns true, but writing

if object.child.valid?

left us vulnerable to the dreaded

# NoMethodError: undefined method `child' for nil:NilClass

They then implemented the 'try' method, which just maps and uncaught exception -> nil (false), and on anything else it just yields control.
if try { object.child.valid? }

This is a common idiom. Another one, taken from the wikipedia page on Icon. http://en.wikipedia.org/wiki/Icon_(programming_language)

For instance, we can write a program to copy an entire input file to output in a single line:

while write(read())

When the read() command fails, at the end of file for instance, the failure will be passed up the chain and write() will fail as well. The while, being a control structure, stops on failure, meaning it stops when the file is empty. For comparison, consider a similar example written in Java-based pseudocode:

try {
while ((a = read()) != EOF) {
write(a);
}
} catch (Exception e) {
// do nothing, exit the loop
}

It's a simple notion - things eventually fail, so just tell the program what to do when it does. Here's another example.

Icon includes several generator-builders. The alternator syntax allows a series of items to be generated in sequence until one fails: 1 | "hello" | x < 5 can generate "1", "hello", and "5" if x is less than 5. Alternators can be read as "or" in many cases, for instance:

if y < (x | 5) then write("y=", y)

You can see how this could have a wonderful synergy with Fortress. Goal directed execution allows you to multicast control flow. Often in combinatorial code, for example, you need to find some solution, but you don't really care which one you get. You could write

if test(possible_solution_generator) then return solution

and be done with it.

Also in exception handling. there may be cases where an elaborate exception heirarchy is really valuable, but in my (limited) experience, just keeping things flat and using message passing is more flexible, and easier. One of the problems is that people, in the Java world, use the exception heirarchy as a method to handle control flow -- some things are unexpected errors, but others just prompt a response. The trouble seems to be that this demands that you define, ahead of time, a class heirarchy of exceptions, and then carefully plan how each function will throw or handle different types. It's a big, front loaded design task, and something that people tend to get wrong on a first pass. Usually you can't forsee errors. I don't know if this is like this in Fortress, but in Java, the idiom is to make a new exception class, and then throw it, mentioning on all possible calling functions how this exception is supposed to be handled. This is a major task. And people pollute the heirarchy with exceptions that are simply 'events' that you need to handle. Frankly, I think this is better solved with co-routines.

-----

Wow. This has gotten way longer than I wanted it to be. But it's 6am now, and I better get some rest. Hopefully, it's not so long that nobody responds, but, if so, I guess it's worth of refinement.

In any case, I wish everyone on the project all the best.

Danielle Fong

daniellefong.com


dmitrey | 29 Jun 17:43

Re: Hello ProjectFortress! Questions and discussion on scripting, pattern matching, goal-directed programming, and other nondeterministic idioms.

Danielle Fong wrote:
>
> I can imagine the computations that slow maple and mathematica down 
> just blazing on Fortress, and that would be really great.
>
IMHO MATLAB and Python are much more serious concurrents than maple and 
mathematica.

> But for it to be a nice scripting language, the syntax seems a little 
> heavy.
>
+1
>
> But then I look at even Hello World and I'm a little daunted, because 
> it still looks like there's an awful lot of code around that isn't 
> core to the meaning.
>
+1. It would be nice to have something
if __run__: print "hello world"
instead.

> So one of my impressions, which I'll just put out here, is that having 
> a 'script mode' might be a very handy thing
>
I also intended to philosophize in the mail list around script mode (BTW 
it would speedup fortress development by itself - it's easy to 
copy-paste some statements to interpreter command prompt ans see the 
result) but it's beyond of my English and seems like fortress developers 
are busy enough with static code implementation yet.
>
> For example, code blocks are delimited with 'end'. Are they needed?
>
The question had been discussed here, I vote for omit the ones but seems 
like majority dislike the idea.
>
> Programmers by default seem to follow the path of least typing. Is 
> there anything in the parser, or in the semantics, that need it?
>
their arguments are "it's a headache to copy-paste from other files with 
TAB-based indentation and/or other languages". There is an opinion all 
Python programmers can be separated into 2 categories : those who like 
indentation-based block style and those who hates it. As for me I found 
it convenient but unfortunately other category is big enough.

Regards, D.

P.S. I also had made some proposition, mostly rejected of course, but 
there is a one issue I can't take: I still consider parallel cycle 
implementation syntax remains the greatest evil of fortress language 
yet. Programmers are provoked to use parallel loops (for i <- 0#n) 
insead of long-to-type sequential ones (for i <- seq(0#n)), and taking 
benefites of about 2% speedup (for ordinary code like read-write from 
files, initial preparation of data and post-processing, i.e. those 
places of code that don't require great speed) it will yield 2x-3x times 
bugs, that will be triggered unpredictably (once per day, week, month, 
year etc) and are hard to be cached. Despite of low list activity, IIRC 
there were similar opinions mentioned (like using for i <- parallel(...) 
and for i <- sequential(...) or for i <- 0#n and for i <= 0#n), but 
seems like fortress developers have ignored it - it's a pity of course.

P.P.S As for me I would concentrate on achieving calculations speed 
comparable to Python one (at least) than implementing those features 
that maybe will hardly be used commonly. speed is the chief feature 
(it's senseless to start programming anything serious while fortress 
speed isn't at least same to Python one) while those experimental 
features could wait.

Python has much less keywords and continue to reduce the ones (for 
example "map", "reduce" were excluded in recent Python ver), and I 
suspect it's the future of some fortress keywords as well.

Danielle Fong | 30 Jun 06:00

Re: Hello ProjectFortress! Questions and discussion on scripting, pattern matching, goal-directed programming, and other nondeterministic idioms.

Hi Dmitrey,

Thanks for the reply.

On Sun, Jun 29, 2008 at 8:43 AM, dmitrey <dmitrey15-qsvJrrU2NvU@public.gmane.org> wrote:
Danielle Fong wrote:

I can imagine the computations that slow maple and mathematica down just blazing on Fortress, and that would be really great.

IMHO MATLAB and Python are much more serious concurrents than maple and mathematica.

I agree. This may be somewhat fundamental. But I don't see any good reason for MATLAB style programs and Mathematica style programs to be kept distinct, and there are still many algorithms, routinely used in Mapla and Mathematica, which could benefit from either data parallelism, or nondeterministic parallelism.

For example, code blocks are delimited with 'end'. Are they needed?

The question had been discussed here, I vote for omit the ones but seems like majority dislike the idea.

I guess I'll search around the lists and read up on the discussion.

Programmers by default seem to follow the path of least typing. Is there anything in the parser, or in the semantics, that need it?

their arguments are "it's a headache to copy-paste from other files with TAB-based indentation and/or other languages". There is an opinion all Python programmers can be separated into 2 categories : those who like indentation-based block style and those who hates it. As for me I found it convenient but unfortunately other category is big enough.

Regards, D.

P.S. I also had made some proposition, mostly rejected of course, but there is a one issue I can't take: I still consider parallel cycle implementation syntax remains the greatest evil of fortress language yet. Programmers are provoked to use parallel loops (for i <- 0#n) insead of long-to-type sequential ones (for i <- seq(0#n)), and taking benefites of about 2% speedup (for ordinary code like read-write from files, initial preparation of data and post-processing, i.e. those places of code that don't require great speed) it will yield 2x-3x times bugs, that will be triggered unpredictably (once per day, week, month, year etc) and are hard to be cached. Despite of low list activity, IIRC there were similar opinions mentioned (like using for i <- parallel(...) and for i &lt ;- sequential(...) or for i <- 0#n and for i <= 0#n), but seems like fortress developers have ignored it - it's a pity of course.

I think that the for i <- 0#n and for i <= 0#n proposal is very interesting. The symbols for <- and <= can be swapped to Unicode to make them look very distinct, so I don't think it would be hard to distinguish while reading. And the two 'shapes' of the operators could imply usage in many other ways. For example, <= (or <=>) could stand for assignment, or pattern matching, while ignoring order, while <- (or <->) requires that you preserve ordering.

P.P.S As for me I would concentrate on achieving calculations speed comparable to Python one (at least) than implementing those features that maybe will hardly be used commonly. speed is the chief feature (it's senseless to start programming anything serious while fortress speed isn't at least same to Python one) while those experimental features could wait.

I'm not sure I agree. Is it an important short term goal to make it popular? And of the people who would want to try to use Fortress now, are they more interested in current performance (in terms of how fast it runs) or are they more interested in potential linguistic improvements over Fortran/SciPy? It seems to me like it should be the latter.
And it's much harder to bring new ideas into a language earlier, than to make a slow language fast. Consider the lexical closure debate in the Java-world.

Python has much less keywords and continue to reduce the ones (for example "map", "reduce" were excluded in recent Python ver), and I suspect it's the future of some fortress keywords as well.

I agree that this is important. Fortress is designed to be growable -- so the core language should really be minimal. Primitives and keywords of less important utility could be removed if they're easy to implement with the remaining primitives, and it could be very worth while.

--
Danielle Fong
415-508-6732
Jon Harrop | 2 Jul 14:34

Re: Hello ProjectFortress! Questions and discussion on scripting, pattern matching, goal-directed programming, and other nondeterministic idioms.

On Monday 30 June 2008 05:00:05 Danielle Fong wrote:
> Hi Dmitrey,
>
> Thanks for the reply.
>
> On Sun, Jun 29, 2008 at 8:43 AM, dmitrey <dmitrey15@...> wrote:
> > Danielle Fong wrote:
> >> I can imagine the computations that slow maple and mathematica down just
> >> blazing on Fortress, and that would be really great.
> >>
> >>  IMHO MATLAB and Python are much more serious concurrents than maple and
> >
> > mathematica.
>
> I agree. This may be somewhat fundamental. But I don't see any good reason
> for MATLAB style programs and Mathematica style programs to be kept
> distinct...

There are huge differences between MATLAB and Mathematica in terms of both 
implementation of programming style/paradigm. MATLAB is a term-level 
interpreter whereas Mathematica is a term rewriter. In terms of style, MATLAB 
encourages conventional imperative style (like Python) whereas Mathematica 
encourages functional and rule-based programming styles. These have 
completely different characteristics, of course. For example, partial 
specialization is very hard with MATLAB and very easy with Mathematica.

The one thing they all have in common is that they are extremely inefficient 
in the general case. You only get decent performance if most of the time is 
spend inside functions written in other languages. Both contain bytecode 
compilers but neither approach the performance of native code that is already 
achievable from many other languages.

I am also surprised that people are not referencing today's most profilific 
functional programming languages given that they are already:

. Extremely fast.

. Extremely concise.

. Widely used for technical computing.

Moreover, Microsoft are pushing F# for this exact purpose and their 
implementation is already of production quality and has excellent support for 
parallelism thanks to .NET.

> and there are still many algorithms, routinely used in Mapla and 
> Mathematica, which could benefit from either data parallelism, or
> nondeterministic parallelism.

I think we can rest assured that Wolfram Research already aggressively 
parallelized their stdlib functions. What they cannot do is retrofit 
parallelism onto the language itself because it is entirely based upon a huge 
mutable data structure (the rule table).

--

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/products/?e

Guy Steele | 1 Jul 19:15

Re: Hello ProjectFortress! Questions and discussion on scripting, pattern matching, goal-directed programming, and other nondeterministic idioms.

Hi, Dmitrey.

On Jun 29, 2008, at 11:43 AM, dmitrey wrote:
...
P.S. I also had made some proposition, mostly rejected of course, but there is a one issue I can't take: I still consider parallel cycle implementation syntax remains the greatest evil of fortress language yet. Programmers are provoked to use parallel loops (for i <- 0#n) insead of long-to-type sequential ones (for i <- seq(0#n)), and taking benefites of about 2% speedup (for ordinary code like read-write from files, initial preparation of data and post-processing, i.e. those places of code that don't require great speed) it will yield 2x-3x times bugs, that will be triggered unpredictably (once per day, week, month, year etc) and are hard to be cached. Despite of low list activity, IIR! C there w ioned (like using for i <- parallel(...) and for i <- sequential(...) or for i <- 0#n and for i <= 0#n), but seems like fortress developers have ignored it - it's a pity of course.

I just want to apologize for the fact that we haven't had much
explicit discussion on this topic so far.  I know it seems as if
the Fortress team at Sun has just ignored the issue of the best way
to mark parallel and sequential loops.  It is very much on our minds,
but it is still not clear what is the best way, so for now we have made
no change and are concentrating on building a compiler.  But we
are thinking about the problem in the background.  It may be
that if we can have better technology in the compiler to detect
"dangerous" parallel loops, so we could better prevent the bugs,
then maybe the syntax problem is not as importa! nt. &nbsp t figure out better compiler analysis, then suggestions such
as yours may become much more important to consider.

I think the choice of <- or <= is very cute, very clever, and visually
appealing, but I worry that it will be too subtle for most programmers
to notice when reading over code quickly.  Still, it is a possible
choice to consider in the future.

--Guy Steele

dmitrey | 1 Jul 20:07

Re: Hello ProjectFortress! Questions and discussion on scripting, pattern matching, goal-directed programming, and other nondeterministic idioms.

hi all,

Guy Steele wrote:
> Hi, Dmitrey.
> [...]
> I just want to apologize for the fact that we haven't had much
> explicit discussion on this topic so far.  I know it seems as if
> the Fortress team at Sun has just ignored the issue of the best way
> to mark parallel and sequential loops.  It is very much on our minds,
> but it is still not clear what is the best way, so for now we have made
> no change and are concentrating on building a compiler. 
I'm very interested in the following issues, could you give a brief answer

1) what is approximate time fortress language require to have about same 
speed as Python (+numpy, C-written array package)? Usually that one is 
about 2 times slower than C or Fortran (with loops - much more slower). 
Don't you have a webpage with, for example, comparison of current 
fortress language implementation speed (for some test problems), speed 
from previous month (or quarter) and some other programming languages 
results? I guess the page would be very popular for visitors.

2) what is approximate time fortress language require to substitute 
something more better instead of current for loops style (parallel vs seq.)?

> I think the choice of <- or <= is very cute, very clever, and visually
> appealing, but I worry that it will be too subtle for most programmers
> to notice when reading over code quickly.  Still, it is a possible
> choice to consider in the future.
>
> --Guy Steele
I guess in the sake of safety it would be better to have <== for 
parallel and <- for sequential cycles. For the sake of safety users 
should be encouraged (via less-to-type) to use sequential cycles, not 
parallel ones.
Also, maybe it would reduce complexity for compiler (especially static 
one) while estimating how to parallelize calculations in best way (for 
several recursive for loops)?

Also, as I had proposed, rendering into "<" with at least 3  dashes 
would be good (for to increase visual difference).

Regards, D.

Norbert Nemec | 1 Jul 23:44

Parallel vs. sequential loops

Hi there,

this short statement by Guy Steele about parallel vs. sequential loops 
in Fortress got me thinking. The discussion has always been about which 
kind of loop should be the default. Making loops parallel by default 
means a risk of subtle errors by non-thinking programmers. Making them 
sequential by default means a potential loss of performance by 
non-thinking programmers. Maybe the best way is the third option: avoid 
a default and make both kinds of loops explicit, distinct, elegant and 
short to type. One first idea would be:

    seqloop i <- 0#n
vs.
    parloop i <- 0#n

alternatives would be
     "seq" vs. "par"
     "loop" vs. "branch"
     "iterate" vs. "branch"

or maybe something completely different. Key point is that neither 
should be simpler to type and the difference should be directly obvious.

On top of this, the compiler could then of course still try to check 
whether the wrong loop type might have been used and print a warning or 
a hint, but it should rather try to make the programmer think than to 
make him lazy.

Greetings,
Norbert

Guy Steele wrote:
> Hi, Dmitrey.
>
> On Jun 29, 2008, at 11:43 AM, dmitrey wrote:
>> ...
>> P.S. I also had made some proposition, mostly rejected of course, but 
>> there is a one issue I can't take: I still consider parallel cycle 
>> implementation syntax remains the greatest evil of fortress language 
>> yet. 
>
> I just want to apologize for the fact that we haven't had much
> explicit discussion on this topic so far.  I know it seems as if
> the Fortress team at Sun has just ignored the issue of the best way
> to mark parallel and sequential loops.  It is very much on our minds,
> but it is still not clear what is the best way, so for now we have made
> no change and are concentrating on building a compiler.  But we
> are thinking about the problem in the background.  It may be
> that if we can have better technology in the compiler to detect
> "dangerous" parallel loops, so we could better prevent the bugs,
> then maybe the syntax problem is not as importa! nt.   t figure out 
> better compiler analysis, then suggestions such
> as yours may become much more important to consider.
>
> I think the choice of <- or <= is very cute, very clever, and visually
> appealing, but I worry that it will be too subtle for most programmers
> to notice when reading over code quickly.  Still, it is a possible
> choice to consider in the future.
>
> --Guy Steele
>

Danielle Fong | 2 Jul 06:53

Re: Parallel vs. sequential loops

Hi Norbert,

Glad to hear you're thinking about this! Let's keep this discussion going.

I've taken to looking at programming languages from a user interface perspective, since, after all, programmers are human. One thing I have been learning from Jef and Aza Raskin's studies is that good interfaces are habituating. They make it easy to learn how to use them my habit, and soon that habit is permanent. Here's an excerpt from http://nitpicker.pbwiki.com/The+Humane+Interface

Rule 1.

An interface should be habituating.

If the interface can be operated habitually then, after you have used it for a while, its use becomes automatic and you can release all your attention to the task you are trying to achieve. Any interface will have elements that are habituating, but the principle here is to make the entire interface habituating.

I think this is important. Fundamentally, once you use a language it should be habitual. You shouldn't get caught on hiccups preventing you from writing what you want to write. It should be a simple matter to translate intent to code.

Python supports this principle by making it so that there's only one obvious way to do things. Since you keep doing it again and again, pretty soon it comes naturally, and you don't need to think about it.

Parallelism now gives you some of these hiccups. Programming parallelism is intrinsically about order of execution. We either need to hide it, like Fortress now does with parallel loops being the default, or we should expose it, and make it easier for programmers to get in the habit of considering order.

My argument is that we should habituate parallelism and ordering concerns, not hide them. I don't think that the programme of hiding them is very feasible. One way to habituate ordering considerations is to expose ordering concerns in many different parts of the language, so that thinking about ordering is *consistent* and *familiar* to programmers. I think, taken together, this is a natural fit. By this method, we use the same concepts over and over and over again, drilling them in until they become habit.

For example, sometimes you'd like to compare exact equivalence (equal reference), isomorphic equivalence (do these object have the same contents in the same order), or set equivalence (does this container contain equal objects, disregarding order). These are different concepts, and a source of major confusion for programmers.

So, make them look different. Make equivalence always look like an =, but annotated on top with the type.

We can show reference equivalence by a three line equals (typed ==, or ===).

We can show isomorphism by, say an equals with the isomorphism relation on it. We can use the default isomorphism, represented by just the regular equals, or we can pass it some other isomorphism, which we put on top.

We can show set equivalence by an equals with a ~ on top. What the tilde implies is that the order doesn't matter.

Likewise, one common operation is assignment. We can do shallow assignment. We can do deep assignment, with the default ordering. But one thing that keeps coming up is that we sometimes want to do assignment with some other ordering. We might like to reverse or sort it. We might not care which order it goes in -- only that the whole set of objects is copied. These sorts of operations are harder to express as generators, reducers, and comprehensions, but we might be able to write express them in a sensible way.

We can do regular assignment by :=. But we could also sort and do the assignment by writing :=< (or :=> or something), which would render to a := with a < or > on top. If we want to pass that a different ordering, we can do that, too. And if we don't care which order the things are put in, we can just do :~=. This could be helpful for speed or optimization.

Also, suppose we're going to do pattern matching, a'la Erlang. Record based pattern matching is quite powerful, but it's not useful for cases where either side is out of order. So, supposing that <=> is our pattern matching predicate (and maybe <:=> is our pattern matching assignment), then <~=> could be our order-ignorant pattern matching operator.

Finally, let's talk again about writing loops. We use the syntax for <- (some generator). But whether order *matters* or not is hidden inside the generator! Why not bring it out?

Think of 'for' as an object which we're trying to push execution tasks into. Then we don't need the <- syntax, we can just 'assign execution' to the execution controller 'for'. Then if we just write
for := (generator),
it implies that order matters... the order of execution represented by the generator can just be preserved.

But if we write
for :~= (generator),
then it implies that order doesn't matter -- the very same as our parallel case.

We could also write
for :=^(ordering) (generator)
and then give the loop instructions on how to order execution. This is more general, and perhaps better, because now we can pass the language specific instructions as to what should execute first. Perhaps there's an acyclic dependence graph in the program? Then just send something handling how to deal with the order of execution for that along. Maybe we'd like to reverse the order of computation. Or maybe the generator represents a list of jobs, and we'd like to sort them based on priority and estimated time to completion. Etc.

There are many other examples. One reason I like this is because it's a nice syntactic place to put order of execution and parallelization concerns, without cluttering up the rest of the code. At the same time, I don't have to rely on my compiler to do voodoo parallelization magic. Before, Fortress seemed to rely on a description of the system, and generators, comprehensions, and reducers to talk to each other about how to parallelize things. Sounds nice, but it also sounds hard, and I don't have so much faith that a method like this will be able find the best parallelization methods, even in principle.

So, that's my proposal. It's a set of language/syntactic tweaks that make the linguistic support of ordering consistent, and therefore habituating. It's a smaller set of ideas for people to learn, and it's a smaller language to implement. And it opens many interesting avenues, including things in pattern matching, and the assignment of execution order.

Would love to hear your comments,

Danielle

On Tue, Jul 1, 2008 at 2:44 PM, Norbert Nemec <Norbert.Nemec.list-Mmb7MZpHnFY@public.gmane.org> wrote:
Hi there,

this short statement by Guy Steele about parallel vs. sequential loops in Fortress got me thinking. The discussion has always been about which kind of loop should be the default. Making loops parallel by default means a risk of subtle errors by non-thinking programmers. Making them sequential by default means a potential loss of performance by non-thinking programmers. Maybe the best way is the third option: avoid a default and make both kinds of loops explicit, distinct, elegant and short to type. One first idea would be:

  seqloop i <- 0#n
vs.
  parloop i <- 0#n

alternatives would be
   "seq" vs. "par"
   "loop" vs. "branch"
   "iterate" vs. "branch"

or maybe something completely different. Key point is that neither should be simpler to type and the difference should be directly obvious.

On top of this, the compiler could then of course still try to check whether the wrong loop type might have been used and print a warning or a hint, but it should rather try to make the programmer think than to make him lazy.

Greetings,
Norbert




Guy Steele wrote:
Hi, Dmitrey.

On Jun 29, 2008, at 11:43 AM, dmitrey wrote:
...
P.S. I also had made some proposition, mostly rejected of course, but there is a one issue I can't take: I still consider parallel cycle implementation syntax remains the greatest evil of fortress language yet.

I just want to apologize for the fact that we haven't had much
explicit discussion on this topic so far.  I know it seems as if
the Fortress team at Sun has just ignored the issue of the best way
to mark parallel and sequential loops.  It is very much on our minds,
but it is still not clear what is the best way, so for now we have made
no change and are concentrating on building a compiler.  But we
are thinking about the problem in the background.  It may be
that if we can have better technology in the compiler to detect
"dangerous" parallel loops, so we could better prevent the bugs,
then maybe the syntax problem is not as importa! nt.   t figure out better compiler analysis, then suggestions such
as yours may become much more important to consider.

I think the choice of <- or <= is very cute, very clever, and visually
appealing, but I worry that it will be too subtle for most programmers
to notice when reading over code quickly.  Still, it is a possible
choice to consider in the future.

--Guy Steele





--
Danielle Fong (.com)
415-508-6732
Russel Winder | 2 Jul 10:06

Re: Parallel vs. sequential loops

On Tue, 2008-07-01 at 21:53 -0700, Danielle Fong wrote:

> I've taken to looking at programming languages from a user interface
> perspective, since, after all, programmers are human. One thing I have
> been learning from Jef and Aza Raskin's studies is that good
> interfaces are habituating. They make it easy to learn how to use them
> my habit, and soon that habit is permanent. Here's an excerpt from 

From a perceptual and cognitive perspective there are significant
differences between user interfaces generally and programming languages,
not least of which is the nature of the language involved -- a user
interface is not a language that is read and written in the way that a
programming language is.  Also the actors in the interaction are
different populations.

There is a large and very solid body of work under the title "Psychology
of Programming" and indeed there is a Psychology of Programming Interest
Group (PPIG) which is associated with various other organization such as
EACE.

If you are looking for material on the human side of programming
languages then that is the best body of work to look at. 

[ . . . ]

> Parallelism now gives you some of these hiccups. Programming
> parallelism is intrinsically about order of execution. We either need
> to hide it, like Fortress now does with parallel loops being the
> default, or we should expose it, and make it easier for programmers to
> get in the habit of considering order.

Underlying all this is the nature of algorithm, the dictionary
definition, the way it is taught, and perhaps most importantly the
thinking model that most practicing programmers have.  Anyone in
training can tell you that multi-threading is probably the biggest
problem that any and all programmer have, and generally get wrong.
Provision of higher-level abstractions to harness parallelism is clearly
needed.  Indeed I wonder if parallelism should be like register
allocation, something that happens but that programmers generally don't
have to worry about.  By providing the right computational model and the
right language for representing algorithms that are not burdened with
sequential constraints, we may be able to avoid programmers having to
think about threads at all.

This is arguably one of the lessons from occam and Erlang.

[ . . . ]

> Finally, let's talk again about writing loops. We use the syntax for
> <- (some generator). But whether order *matters* or not is hidden
> inside the generator! Why not bring it out?

One of the problems here is the very nature of for.  Historically for is
a synonym for a while construct.  There is an inherent sequential nature
to the while and hence for concept in most people's minds.  Thus rather
than tinker with minor feature of the overall syntax, shouldn't the
argument go something like:  for is a sequential looping construct,
everyone knows this so let's stick to that to avoid any conflict.  What
is needed is a parallel fork operation which creates an "array" of some
number of actions for later joining.  This is not a for construct in the
classic sense it is a par construct so let us use a new keyword so as to
put, front and centre (or center if you prefer :-), the fact that this
construct has significantly different semantics.  Something like the
following perhaps.

	par ( int i = 0 ; i < n ) { . . . }

I don't think anyone could get confused between this and sequential
iteration?

[ . . . ]

--

-- 
Russel.
====================================================
Dr Russel Winder                 Partner

Concertant LLP                   t: +44 20 7585 2200, +44 20 7193 9203
41 Buckmaster Road,              f: +44 8700 516 084
London SW11 1EN, UK.             m: +44 7770 465 077
Alex Battisti | 2 Jul 10:20

Re: Parallel vs. sequential loops

> My argument is that we should habituate parallelism and ordering concerns,
> not hide them. I don't think that the programme of hiding them is very
> feasible. One way to habituate ordering considerations is to expose ordering
> concerns in many different parts of the language, so that thinking about
> ordering is *consistent* and *familiar* to programmers.

you could also imply ordering by using comprehensions for lists and
sets in the for loop.

e.g.
order is relevant -> "list comprehension" -> sequential execution:
for i <- [0#n]

order is irrelevant -> "set comprehension" ->  parallel execution:
for i <- {0#n}

( personally I would prefer a syntax more in the line of: for i <- [0..n] )

maybe there are "interesting semantics for vector (or other) comprehensions"
regarding looping and parallelism?

Alex

dmitrey | 2 Jul 11:00

Re: Parallel vs. sequential loops

I had been thought too about pass parallel flag via type of argument 
(like list vs sequence below).

On the one hand, it would be convenient because one could easily switch:
if some_cond: loop_arg=[...]#use sequential loop
else: loop_arg={...}#use parallel loop
for i <- loop_arg: ...
so only one "for" statement is required.

But drawback is: it will be hard for other programmers to understand is 
it parallel loop or sequential (in code from above stack and/or written 
by another programmer), and hence it can lead to bugs (because they can 
consider that code above has no parallel loops).
D.

Alex Battisti wrote:
>> My argument is that we should habituate parallelism and ordering concerns,
>> not hide them. I don't think that the programme of hiding them is very
>> feasible. One way to habituate ordering considerations is to expose ordering
>> concerns in many different parts of the language, so that thinking about
>> ordering is *consistent* and *familiar* to programmers.
>>     
>
> you could also imply ordering by using comprehensions for lists and
> sets in the for loop.
>
> e.g.
> order is relevant -> "list comprehension" -> sequential execution:
> for i <- [0#n]
>
> order is irrelevant -> "set comprehension" ->  parallel execution:
> for i <- {0#n}
>
> ( personally I would prefer a syntax more in the line of: for i <- [0..n] )
>
> maybe there are "interesting semantics for vector (or other) comprehensions"
> regarding looping and parallelism?
>
> Alex
>
>
>
>   

dmitrey | 2 Jul 11:29

Re: Parallel vs. sequential loops

Norbert Nemec wrote:
>    seqloop i <- 0#n
> vs.
>    parloop i <- 0#n
but how would you translate current style
for i<- 0#n, j<-seq(0#m) do {...} ?

Norbert Nemec | 2 Jul 19:22

Re: Parallel vs. sequential loops

dmitrey wrote:
> Norbert Nemec wrote:
>>    seqloop i <- 0#n
>> vs.
>>    parloop i <- 0#n
> but how would you translate current style
> for i<- 0#n, j<-seq(0#m) do {...} ?

Split it up into two nested loops. The situation you point out is rather 
specialized so a shorthand is not necessary. Forcing the programmer to 
be a bit more explicit also makes the code more readable and shows that 
this mixture of sequential and parallel loop was done on purpose.

dmitrey | 2 Jul 21:01

Re: Parallel vs. sequential loops

Norbert Nemec wrote:
> dmitrey wrote:
>> Norbert Nemec wrote:
>>>    seqloop i <- 0#n
>>> vs.
>>>    parloop i <- 0#n
>> but how would you translate current style
>> for i<- 0#n, j<-seq(0#m) do {...} ?
>
> Split it up into two nested loops. The situation you point out is 
> rather specialized so a shorthand is not necessary. Forcing the 
> programmer to be a bit more explicit also makes the code more readable 
> and shows that this mixture of sequential and parallel loop was done 
> on purpose.
I can't agree that the situation is rather specialized - look into 
fortress library, even now it already has a number of the cases. I had 
deal with lots of code (othe rprogramming languages) with several nested 
loops (as well as lots of other programmers, that are certainly present 
in the list as well) and it's really headache doing several for i for j 
for k ... end end end. I find current style very convenient, and it 
requires only single "end" statement while yours will require several.

Regards, D.

Norbert Nemec | 5 Jul 09:28

Re: Parallel vs. sequential loops

dmitrey wrote:
> Norbert Nemec wrote:
>> dmitrey wrote:
>>> Norbert Nemec wrote:
>>>>    seqloop i <- 0#n
>>>> vs.
>>>>    parloop i <- 0#n
>>> but how would you translate current style
>>> for i<- 0#n, j<-seq(0#m) do {...} ?
>>
>> Split it up into two nested loops. The situation you point out is 
>> rather specialized so a shorthand is not necessary. Forcing the 
>> programmer to be a bit more explicit also makes the code more 
>> readable and shows that this mixture of sequential and parallel loop 
>> was done on purpose.
> I can't agree that the situation is rather specialized - look into 
> fortress library, even now it already has a number of the cases. I had 
> deal with lots of code (othe rprogramming languages) with several 
> nested loops (as well as lots of other programmers, that are certainly 
> present in the list as well) and it's really headache doing several 
> for i for j for k ... end end end. I find current style very 
> convenient, and it requires only single "end" statement while yours 
> will require several.
Sure, nested loops are very common. But how often do you actually mix 
parallel and sequential loops? Maybe I'm wrong in my assumption, but I 
still think that the difference between parallel and sequential loops 
should be more obvious that just a subtle seq().

Apart from that, I the semantics of seq() seem rather strange to me 
anyway. If 0#n designates a set that has no order defined, how does the 
function seq() define an order on top of it?

Danielle Fong | 5 Jul 09:33

Re: Parallel vs. sequential loops

Isn't a mix of parallel and sequential loops one of the main patterns? A sequential timestep, and a parallel calculation?

On Sat, Jul 5, 2008 at 12:28 AM, Norbert Nemec <Norbert.Nemec.list-Mmb7MZpHnFY@public.gmane.org> wrote:
dmitrey wrote:
Norbert Nemec wrote:
dmitrey wrote:
Norbert Nemec wrote:
  seqloop i <- 0#n
vs.
  parloop i <- 0#n
but how would you translate current style
for i<- 0#n, j<-seq(0#m) do {...} ?

Split it up into two nested loops. The situation you point out is rather specialized so a shorthand is not necessary. Forcing the programmer to be a bit more explicit also makes the code more readable and shows that this mixture of sequential and parallel loop was done on purpose.
I can't agree that the situation is rather specialized - look into fortress library, even now it already has a number of the cases. I had deal with lots of code (othe rprogramming languages) with several nested loops (as well as lots of other programmers, that are certainly present in the list as well) and it's really headache doing several for i for j for k ... end end end. I find current style very convenient, and it requires only single "end" statement while yours will require several.
Sure, nested loops are very common. But how often do you actually mix parallel and sequential loops? Maybe I'm wrong in my assumption, but I still think that the difference between parallel and sequential loops should be more obvious that just a subtle seq().

Apart from that, I the semantics of seq() seem rather strange to me anyway. If 0#n designates a set that has no order defined, how does the function seq() define an order on top of it?



--
Danielle Fong (.com)
415-508-6732
Jon Harrop | 5 Jul 15:36

Re: Parallel vs. sequential loops

On Saturday 05 July 2008 08:33:24 Danielle Fong wrote:
> Isn't a mix of parallel and sequential loops one of the main patterns? A
> sequential timestep, and a parallel calculation?

I would say that the most common pattern in parallelism is recursive 
subdivision. Even when you have a parallel "for" loop you want to chunk the 
input to ensure that the cost of spawning a parallel computation is 
insignificant compared to the work each parallel computation will do 
otherwise you are facing a pathological case with catastrophic performance 
loss.

Hence the naive parallel "for" loop (which has been the sole topic of 
discussion here) is a bad idea.

--

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/products/?e

David Chase | 5 Jul 18:07

Re: Parallel vs. sequential loops


You should know, that a large amount of work (most of it Jan's, but  
distributed onto the rest of the team as he generates interpreter and  
type-system problems along the way) has been devoted to the design of  
"generators", so that when you write the so-called "naive parallel for  
loop" what you get is in fact, recursive subdivision, preferably with  
the subdivision chosen to be "best" for the data that is driving the  
parallelism.

David

On 2008-07-05, at 9:36 AM, Jon Harrop wrote:

> On Saturday 05 July 2008 08:33:24 Danielle Fong wrote:
>> Isn't a mix of parallel and sequential loops one of the main  
>> patterns? A
>> sequential timestep, and a parallel calculation?
>
> I would say that the most common pattern in parallelism is recursive
> subdivision. Even when you have a parallel "for" loop you want to  
> chunk the
> input to ensure that the cost of spawning a parallel computation is
> insignificant compared to the work each parallel computation will do
> otherwise you are facing a pathological case with catastrophic  
> performance
> loss.

> Hence the naive parallel "for" loop (which has been the sole topic of
> discussion here) is a bad idea.

Jon Harrop | 6 Jul 23:29

Re: Parallel vs. sequential loops

On Saturday 05 July 2008 17:07:06 David Chase wrote:
> You should know, that a large amount of work (most of it Jan's, but
> distributed onto the rest of the team as he generates interpreter and
> type-system problems along the way) has been devoted to the design of
> "generators", so that when you write the so-called "naive parallel for
> loop" what you get is in fact, recursive subdivision, preferably with
> the subdivision chosen to be "best" for the data that is driving the
> parallelism.

Exactly. The "best" subdivision must be guided by cost information and these 
naive parallel "for" loops fail to convey any such information. So the 
subdivision is completely naive and, consequently, the only parallel 
construct that has been discussed here so far will silently introduce 
catastrophic performance degradation into user code when they least expect 
it.

--

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/products/?e

Danielle Fong | 7 Jul 00:11

Re: Parallel vs. sequential loops

the only parallel construct that has been discussed here so far will silently introduce
catastrophic performance degradation into user code when they least expect it.

Isn't that a little premature? On the other hand, I would like some pointers to Jan's design work. This part really fascinates me but it seems very complex.

On Sun, Jul 6, 2008 at 2:29 PM, Jon Harrop <jon-gOjbu7hk+9q6HovDotTUrQC/G2K4zDHf@public.gmane.org> wrote:
On Saturday 05 July 2008 17:07:06 David Chase wrote:
> You should know, that a large amount of work (most of it Jan's, but
> distributed onto the rest of the team as he generates interpreter and
> type-system problems along the way) has been devoted to the design of
> "generators", so that when you write the so-called "naive parallel for
> loop" what you get is in fact, recursive subdivision, preferably with
> the subdivision chosen to be "best" for the data that is driving the
> parallelism.

Exactly. The "best" subdivision must be guided by cost information and these
naive parallel "for" loops fail to convey any such information. So the
subdivision is completely naive and, consequently, the only parallel
construct that has been discussed here so far will silently introduce
catastrophic performance degradation into user code when they least expect
it.

--
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/products/?e



--
Danielle Fong (.com)
415-508-6732
Jon Harrop | 7 Jul 03:04

Re: Parallel vs. sequential loops

On Sunday 06 July 2008 23:11:21 Danielle Fong wrote:
> > the only parallel construct that has been discussed here so far will
> > silently introduce catastrophic performance degradation into user code
> > when they least expect it. 
>
> Isn't that a little premature?

I don't believe so. For example, this is an issue we fixed in our products 
some time ago.

--

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/products/?e

David Chase | 7 Jul 02:34

Re: Parallel vs. sequential loops


On 2008-07-06, at 5:29 PM, Jon Harrop wrote:
>
> Exactly. The "best" subdivision must be guided by cost information  
> and these
> naive parallel "for" loops fail to convey any such information. So the
> subdivision is completely naive and, consequently, the only parallel
> construct that has been discussed here so far will silently introduce
> catastrophic performance degradation into user code when they least  
> expect
> it.

Just checking, but you do understand that if I write

   for i <- a.indices do
      ... something with a[i] ...
   end

that the subdivision chosen is "best" for access to a, because
it is chosen by a?

When you say "naive", is that what you are talking about?
In practice (from the talks I've seen) even truly naive recursive
subdivision is substantially better than "catastrophic".  Plain
old count-from-the-beginning iteration, that's what I call  
"catastrophic".

Yes, there are still problems to solve, but I want to be sure that we
understand each other.

And, if you do think that this is "catastrophic" even for the example
I provide above, what would YOU write?  Can you figure out a way to do
it that still resembles "what a mathematician would write"?

David

Jon Harrop | 7 Jul 18:36

Re: Parallel vs. sequential loops

On Monday 07 July 2008 01:34:02 David Chase wrote:
> On 2008-07-06, at 5:29 PM, Jon Harrop wrote:
> > Exactly. The "best" subdivision must be guided by cost information
> > and these
> > naive parallel "for" loops fail to convey any such information. So the
> > subdivision is completely naive and, consequently, the only parallel
> > construct that has been discussed here so far will silently introduce
> > catastrophic performance degradation into user code when they least
> > expect
> > it.
>
> Just checking, but you do understand that if I write
>
>    for i <- a.indices do
>       ... something with a[i] ...
>    end
>
> that the subdivision chosen is "best" for access to a, because
> it is chosen by a?

I don't understand what you mean by this. Are you saying that the subdivision 
tries to improve locality of reference to the elements of "a" in order to 
optimize cache coherence?

So you are assuming that the element type of the array is a value type?

What happens if the element type is a reference type (e.g. an array of big 
ints) or if the array "a" is sparse?

> When you say "naive", is that what you are talking about?
> In practice (from the talks I've seen) even truly naive recursive
> subdivision is substantially better than "catastrophic".  Plain
> old count-from-the-beginning iteration, that's what I call
> "catastrophic".
>
> Yes, there are still problems to solve, but I want to be sure that we
> understand each other.
>
> And, if you do think that this is "catastrophic" even for the example
> I provide above, what would YOU write?  

The catastrophic performance loss I was referring to occurs when the spawning 
of parallel computations becomes the bottleneck. This is "catastrophic" 
because the cost of queuing a work item is orders of magnitude larger than 
the cost of a primitive operation (e.g. int addition). This pitfall must be 
avoided at all costs.

The naive parallel "for" loop we are discussing has no idea what the 
complexity of the body of the loop is, so its options are very limited. It 
might spawn a parallel computation for every iteration of the loop, in which 
case a trivial loop body (e.g. vector dot product) will certainly suffer the 
catastrophic performance loss because one MLA is much faster than queuing a 
work item, so the parallel vector dot product is likely to be a thousand 
times slower than the serial version. At the other end of the spectrum, it 
might evenly divide the indices across each computation unit but that does 
not distribute computation at all well and will give poor results if the work 
performed per iteration is not constant, e.g. iteration over a triangle:

  for i=1 to n do
    for j=1 to i do
      ...

The solution is to pass a cost function that gives an estimate of the minimum 
complexity of the loop body for a given range of indices. The parallel 
construct can recursively bisect the range of indices while the cost function 
is sufficiently high and then resort to serial iteration. Moreover, this 
approach transcends loops over arrays and also works well with parallel 
traversal of trees and familiar parallel constructs like map-reduce.

> Can you figure out a way to do it that still resembles "what a mathematician
> would write"? 

Mathematicians have no notion of parallel execution so that seems like a 
fruitless goal to me. However, working out a cost function and passing it as 
an argument in a functional language is easy enough:

  parfor 1 n (fun i0 i1 -> (i1-1)*i1/2-(i0-1)*i0/2) (fun i ->
    for j=1 to i do
      ...)

Now the parallel construct has a much better chance of performing effective 
subdivision for efficient parallelization. This also facilitates a variety of 
more sophisticated approaches.

AFAIK, there is no better way to do this in the general case. My code examples 
are working F# BTW.

--

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/products/?e

Re: Parallel vs. sequential loops

On Mon, Jul 7, 2008 at 7:36 PM, Jon Harrop <jon@...> wrote:
> [...]
> I don't understand what you mean by this. Are you saying that the subdivision
> tries to improve locality of reference to the elements of "a" in order to
> optimize cache coherence?
>
> So you are assuming that the element type of the array is a value type?
>
> What happens if the element type is a reference type (e.g. an array of big
> ints) or if the array "a" is sparse?
> [...]

I am definitely not the best person to answer this and I have little
experience here, but I think that if you read the documentation about
Generator, you can understand that this is exactly what Fortress does:
they have a different implementation for sparse data structures, a
different one for "regular" arrays, and anyone can implement their
own, no matter how specific it is. Each implementation can choose
exactly how it wants to split to threads or be completely serial, and
seq(generator) should always be serial, when you need it.

> The catastrophic performance loss I was referring to occurs when the spawning
> of parallel computations becomes the bottleneck. This is "catastrophic"
> because the cost of queuing a work item is orders of magnitude larger than
> the cost of a primitive operation (e.g. int addition). This pitfall must be
> avoided at all costs.
>
> The naive parallel "for" loop we are discussing has no idea what the
> complexity of the body of the loop is, so its options are very limited. It
> might spawn a parallel computation for every iteration of the loop, in which
> case a trivial loop body (e.g. vector dot product) will certainly suffer the
> catastrophic performance loss because one MLA is much faster than queuing a
> work item, so the parallel vector dot product is likely to be a thousand
> times slower than the serial version. At the other end of the spectrum, it
> might evenly divide the indices across each computation unit but that does
> not distribute computation at all well and will give poor results if the work
> performed per iteration is not constant, e.g. iteration over a triangle:
>
>  for i=1 to n do
>    for j=1 to i do
>      ...
>
> The solution is to pass a cost function that gives an estimate of the minimum
> complexity of the loop body for a given range of indices. The parallel
> construct can recursively bisect the range of indices while the cost function
> is sufficiently high and then resort to serial iteration. Moreover, this
> approach transcends loops over arrays and also works well with parallel
> traversal of trees and familiar parallel constructs like map-reduce.
>

This seems an interesting proposal: as far as I can tell, generators
optimize based on the data structure that gets iterated over; why not
also optimize based on the body of the "for"?

>> Can you figure out a way to do it that still resembles "what a mathematician
>> would write"?
>
> Mathematicians have no notion of parallel execution so that seems like a
> fruitless goal to me. However, working out a cost function and passing it as
> an argument in a functional language is easy enough:
>
>  parfor 1 n (fun i0 i1 -> (i1-1)*i1/2-(i0-1)*i0/2) (fun i ->
>    for j=1 to i do
>      ...)
>
> Now the parallel construct has a much better chance of performing effective
> subdivision for efficient parallelization. This also facilitates a variety of
> more sophisticated approaches.
>
> AFAIK, there is no better way to do this in the general case. My code examples
> are working F# BTW.

So F# doesn't have such optimizations already built into the language/libraries.

>
> --
> Dr Jon D Harrop, Flying Frog Consultancy Ltd.
> http://www.ffconsultancy.com/products/?e
>

Doug Lea | 8 Jul 02:30

Re: Parallel vs. sequential loops

Jon Harrop wrote:

> The catastrophic performance loss I was referring to occurs when the spawning 
> of parallel computations becomes the bottleneck. This is "catastrophic" 
> because the cost of queuing a work item is orders of magnitude larger than 
> the cost of a primitive operation (e.g. int addition). This pitfall must be 
> avoided at all costs.

There are several pitfalls to avoid in generating parallel computation,
and this is certainly one of them, but as one of the people working
in the basement (the underlying fork/join engine) I think the effects
are short of "catastrophic", and are likely to be manageable.

For example, here's a table of run times for the standard parallel
recursive Fibonacci program under different sequential thresholds.
The threshold is the fib argument value at which point you choose
sequential over parallel computation. This table is for fib(42)
running on a T2 Niagara with 64 threads. ST=42 runs purely
sequentially. ST=2 is the smallest possible value (0 and 1 are
base cases).

ST    time (sec)
42    14.770
40:    5.644
38:    2.160
36:    0.912
34:    0.516
32:    0.507
30:    0.440
28:    0.420
26:    0.410
24:    0.408
22:    0.407
20:    0.407
18:    0.410
16:    0.412
14:    0.417
12:    0.435
10:    0.466
  8:    0.593
  6:    0.834
  4:    1.479
  2:    3.366

The U-shaped curve with a wide shallow middle and steep sides seems
typical of most work-stealing applications I've seen. Note that even
here, the penalty for over-partitioning is much less than not
partitioning at all. (A few further per-task overhead reductions,
that further push out the edges are also in the works.)

The fact that there is a wide range of nearly-equivalent thresholds
makes me optimistic about dynamic techniques that don't
require user guidance to stay away from the edges. The fork/join
framework used by Fortress already supports some capabilities
along these lines, with more on the way. For example, we've
gotten very promising results for parallel graph traversal
by sensing and exploiting work-stealing queue depths and steal
rates.

-Doug

Eugene Loh | 5 Jul 17:58

Re: Parallel vs. sequential loops

Danielle Fong wrote:

> Isn't a mix of parallel and sequential loops one of the main patterns? 
> A sequential timestep, and a parallel calculation?

Yes, but (IMHO) one wouldn't want to write both loops together.  You'd 
have a sequential loop and inside it you would have parallel loops.  You 
wouldn't want to write both constructs together.

Just my opinion.

P.S.  (I work for Sun, but am not a member of the Fortress team.)

dmitrey | 5 Jul 10:15

Re: Parallel vs. sequential loops

Norbert Nemec wrote:
> dmitrey wrote:
>> Norbert Nemec wrote:
>>> dmitrey wrote:
>>>> Norbert Nemec wrote:
>>>>>    seqloop i <- 0#n
>>>>> vs.
>>>>>    parloop i <- 0#n
>>>> but how would you translate current style
>>>> for i<- 0#n, j<-seq(0#m) do {...} ?
>>>
>>> Split it up into two nested loops. The situation you point out is 
>>> rather specialized so a shorthand is not necessary. Forcing the 
>>> programmer to be a bit more explicit also makes the code more 
>>> readable and shows that this mixture of sequential and parallel loop 
>>> was done on purpose.
>> I can't agree that the situation is rather specialized - look into 
>> fortress library, even now it already has a number of the cases. I 
>> had deal with lots of code (othe rprogramming languages) with several 
>> nested loops (as well as lots of other programmers, that are 
>> certainly present in the list as well) and it's really headache doing 
>> several for i for j for k ... end end end. I find current style very 
>> convenient, and it requires only single "end" statement while yours 
>> will require several.
> Sure, nested loops are very common. But how often do you actually mix 
> parallel and sequential loops? Maybe I'm wrong in my assumption, but I 
> still think that the difference between parallel and sequential loops 
> should be more obvious that just a subtle seq().
Despite I have no much fortress code written I have already used the 
mix, for example here's my vandermonde matrix generator written  2007/04/25

vander[\nat n\](C) = do
M : RR64[n,n]  := array2[\RR64,n,n\](1)
for j <- 0#n do M[j, n-1] := 1 end
for j <- 0#n, i <- seq(1#n-1) do (*<- parallel and sec loop here*)
M[j, n-i-1] := C[j] M[j,n-i]
end
M
end

Regards, D.

Norbert Nemec | 5 Jul 21:32

Re: Parallel vs. sequential loops

dmitrey wrote:
> Norbert Nemec wrote:
>> dmitrey wrote:
>>> Norbert Nemec wrote:
>>>> dmitrey wrote:
>>>>> Norbert Nemec wrote:
>>>>>>    seqloop i <- 0#n
>>>>>> vs.
>>>>>>    parloop i <- 0#n
>>>>> but how would you translate current style
>>>>> for i<- 0#n, j<-seq(0#m) do {...} ?
>>>>
>>>> Split it up into two nested loops. The situation you point out is 
>>>> rather specialized so a shorthand is not necessary. Forcing the 
>>>> programmer to be a bit more explicit also makes the code more 
>>>> readable and shows that this mixture of sequential and parallel 
>>>> loop was done on purpose.
>>> I can't agree that the situation is rather specialized - look into 
>>> fortress library, even now it already has a number of the cases. I 
>>> had deal with lots of code (othe rprogramming languages) with 
>>> several nested loops (as well as lots of other programmers, that are 
>>> certainly present in the list as well) and it's really headache 
>>> doing several for i for j for k ... end end end. I find current 
>>> style very convenient, and it requires only single "end" statement 
>>> while yours will require several.
>> Sure, nested loops are very common. But how often do you actually mix 
>> parallel and sequential loops? Maybe I'm wrong in my assumption, but 
>> I still think that the difference between parallel and sequential 
>> loops should be more obvious that just a subtle seq().
> Despite I have no much fortress code written I have already used the 
> mix, for example here's my vandermonde matrix generator written  
> 2007/04/25
>
> vander[\nat n\](C) = do
> M : RR64[n,n]  := array2[\RR64,n,n\](1)
> for j <- 0#n do M[j, n-1] := 1 end
> for j <- 0#n, i <- seq(1#n-1) do (*<- parallel and sec loop here*)
> M[j, n-i-1] := C[j] M[j,n-i]
> end
> M
> end
Sorry, but that seems to be an unlucky example, since it can be 
expressed even more concise as:

vander[\nat n\](C) = do
M : RR64[n,n]  := array2[\RR64,n,n\](1)
for j <- 0#n do
M[j, n-1] := 1
for i <- seq(1#n-1) do
M[j, n-i-1] := C[j] M[j,n-i]
end
end
M
end

But in any case: I still think that it makes sense to sacrifice the 
possibility of mixing parallel and sequential loops in favor of a more 
explicit syntax that aids readability and avoids offering a dangerous 
implicit default.

dmitrey | 6 Jul 11:33

Re: Parallel vs. sequential loops

Norbert Nemec wrote:
> dmitrey wrote:
>> Norbert Nemec wrote:
>>> dmitrey wrote:
>>>> Norbert Nemec wrote:
>>>>> dmitrey wrote:
>>>>>> Norbert Nemec wrote:
>>>>>>>    seqloop i <- 0#n
>>>>>>> vs.
>>>>>>>    parloop i <- 0#n
>>>>>> but how would you translate current style
>>>>>> for i<- 0#n, j<-seq(0#m) do {...} ?
>>>>>
>>>>> Split it up into two nested loops. The situation you point out is 
>>>>> rather specialized so a shorthand is not necessary. Forcing the 
>>>>> programmer to be a bit more explicit also makes the code more 
>>>>> readable and shows that this mixture of sequential and parallel 
>>>>> loop was done on purpose.
>>>> I can't agree that the situation is rather specialized - look into 
>>>> fortress library, even now it already has a number of the cases. I 
>>>> had deal with lots of code (othe rprogramming languages) with 
>>>> several nested loops (as well as lots of other programmers, that 
>>>> are certainly present in the list as well) and it's really headache 
>>>> doing several for i for j for k ... end end end. I find current 
>>>> style very convenient, and it requires only single "end" statement 
>>>> while yours will require several.
>>> Sure, nested loops are very common. But how often do you actually 
>>> mix parallel and sequential loops? Maybe I'm wrong in my assumption, 
>>> but I still think that the difference between parallel and 
>>> sequential loops should be more obvious that just a subtle seq().
>> Despite I have no much fortress code written I have already used the 
>> mix, for example here's my vandermonde matrix generator written  
>> 2007/04/25
>>
>> vander[\nat n\](C) = do
>> M : RR64[n,n]  := array2[\RR64,n,n\](1)
>> for j <- 0#n do M[j, n-1] := 1 end
>> for j <- 0#n, i <- seq(1#n-1) do (*<- parallel and sec loop here*)
>> M[j, n-i-1] := C[j] M[j,n-i]
>> end
>> M
>> end
> Sorry, but that seems to be an unlucky example, since it can be 
> expressed even more concise as:
>
> vander[\nat n\](C) = do
> M : RR64[n,n]  := array2[\RR64,n,n\](1)
> for j <- 0#n do
> M[j, n-1] := 1
> for i <- seq(1#n-1) do
> M[j, n-i-1] := C[j] M[j,n-i]
> end
> end
> M
> end
>
> But in any case: I still think that it makes sense to sacrifice the 
> possibility of mixing parallel and sequential loops in favor of a more 
> explicit syntax that aids readability and avoids offering a dangerous 
> implicit default.
Ok, I would say it could be simplified even more: since the array M is 
already filled with ones, the line M[j, n-1] := 1 can be omitted, hence 
we get

vander[\nat n\](C) = do
M : RR64[n,n]  := array2[\RR64,n,n\](1)
for j <- 0#n, i <- seq(1#n-1) do (*<- parallel and sec loop here*)
M[j, n-i-1] := C[j] M[j,n-i]
end
M
end

As for your suggestion, the drawback is: suppose we have some condition 
can_use_parallel, and code

if can_use_parallel:
parfor i <- [...] do [...] end
else: for i <- [...] do [same code as above, hence code duplicating] end

One of the solutions mentioned was type of cycle arg (like for example 
set vs sequence), but it can be sometimes not clear to code readers 
and/or other programmers are here parallel or sequential calculations 
take place.

Regards, D.

Ted Kosan | 1 Jul 04:15

Re: Hello ProjectFortress! Questions and discussion on scripting, pattern matching, goal-directed programming, and other nondeterministic idioms.

Danielle wrote:

> There are what seem like competing goals you're working under. HPC seems to
> demand things like unboxed variables, and static type checking, and rock
> solid exception handling, and a great security model.
>
> On the other hand, I see great potential in Fortress, or a Fortress derived
> language, as a *scripting* language for scientists and mathematicians. I can
> imagine something like it replacing Mathematica as the default first
> language people learn, and the default language people work and think in.
> This is partly because the parser and character set are so powerful, and the
> language is so extensible, that the natural expression of many statements
> will, with the right libraries, take little effort to translate into
> Fortress. Also, with parallelism and easily parallelized constructs built so
> deeply into the language, I can imagine the computations that slow maple and
> mathematica down just blazing on Fortress, and that would be really great.
>
> But for it to be a nice scripting language, the syntax seems a little heavy.
> At least at first, that's my impression. I look at the conjugate gradient
> example and think 'my, that's beautiful, it's like the canonical encoding of
> what that algorithm *means*'. But then I look at even Hello World and I'm a
> little daunted, because it still looks like there's an awful lot of code
> around that isn't core to the meaning. What's all this 'export' and
> 'executable' and run =, and component stuff? The canonical representation of
> hello world is 'print "hello world"'!
>
> One of the core philosophies of the scripting world is that you can get up
> and running in an extremely short time. And then it's a really, really
> simple matter to move next to simple calculations, and variable assignment,
> and loops, and so on. Access to a powerful and helpful interactive
> interpreter/debugger, like Python, or Mathematica, is really helpful too (if
> you go one step further, and typeset code and equations, it will be even
> better).
>
> These features make a smooth gradient for absolute beginners to climb.
> Eventually, they'll get all the way up to being able to build library code.
> I think that smoothing that gradient out could really be crucial for
> Fortress. A goal should be that it's a language people will want to teach
> students *first*. It is so different from previous languages that I see only
> a few of the old guard, those really in the know, switching -- the rest will
> have to pick it up early on. But that's okay, you have long term goals.

I am glad you made these "big picture" observations because I think
they are important.  One reason I think this is because I have spent
the past year studying the following three open source mathematics
software projects and I think each one holds valuable information that
project Fortress can benefit from:

   1) Maxima (http://maxima.sourceforge.net)
   2) Axiom (http://wiki.axiom-developer.org)
   3) Sage (http://sagemath.org)

For example, all of these mathematics applications provide an
interactive scripting mode and yet each one still fails to provide "a
smooth gradient for absolute beginners to climb" (BTW, I really like
your "smooth gradient for beginners" concept because it is easy to
explain to people).  One would think that the goal of providing an
easy way for beginners to start learning these applications would be
an obvious one and I became very curious as to why all three projects
were deficient in this area.

One reason I discovered can be described by imagining each of these
applications as being a steep mountain.  Typical uses are like
seasoned mountain climbers who are well equipped to scale even the
steepest cliff faces.  Prospective users are assumed to be capable
"mountain climbers" who only need a relatively small amount of
information on local conditions before beginning.  Less capable people
are not catered to because they would need a ramp so long that it
would be very difficult for most open source projects to build it.  I
learned this the hard way while I was writing a book called "Sage for
Newbies" (http://sage.math.washington.edu/home/tkosan/newbies_book).

I became somewhat obsessed with this "beginners" problem and I
eventually came up with an alternative solution which does not require
building a large "ramp".  The alternative solves the problem at a
community level instead of at a project level.  Instead of starting
absolute beginners on the big mountain, have the community provide a
hill for them to train on and design the hill so that the skills
learned on it are those which are needed on the mountain.

To