gracelang / language

Design of the Grace language and its libraries
GNU General Public License v2.0
6 stars 1 forks source link

Functional Programming with Lists in Grace #112

Open KimBruce opened 8 years ago

KimBruce commented 8 years ago

Consider the following program in Grace that was written as part of a functional programming solution to the recurring rainfall problem:

method functional(frl: List[[Number]]) -> Number {
    def goodData: List[[Number]] = list (frl.filter{val: Number -> val >= 0})
    def finalGood: Number = goodData.indexOf(-999) ifAbsent {goodData.size+1} - 1

    if (finalGood == 0) then { 
        -999   // flag to signal an error - can't find average of 0 elements
    } else {
        sumList(goodData,finalGood) / finalGood
    }
}

This program compiles and runs (assuming sumList is defined), but if you omit the "list" in the definition of goodData, then you get a run-time error, complaining that the result of fro.filter{...} is not a list.

Let me emphasize that this is a dynamic error, there is no static type checking going on. The problem is that filter is defined on Enumerable, and returns an Enumerable, not a list. As a result one must convert the result to a list, either by using the "constructor" list, or by sending the result the asList method request.

This is an interesting case as one can attempt to fix the static typing by making the return type of "filter" be MyType, but it does not fix the actual type of the object returned. (In fact, there would be a static type checking error in the definition of filter as it really does return an Enumerable, NOT MyType.

I've created a separate issue for this as it is a more serious design issue if we really want to solve this to make functional programming with lists work properly in Grace. (I also don't believe it is high priority.) In the meantime, applying the list constructor or requesting the asList method are simple work-arounds (though they are not particularly efficient for run-time).

kjx commented 8 years ago

yep. I am unsurprised - that distinction is tricky, and lineups being Enumerable makes it worse. I think we can simplify things - I've been looking at Java8 Streams but haven't finished yet.

apblack commented 7 years ago

The reason that filter answers an enumerable is exactly to permit efficient functional programming! One could certainly make the result of filter support indexing, but that either requires reifying all of the intermediate objects when using the pipe and filter style, or doing some very clever programming (which I think is what Java 8 does), reifying and caching on demand.

Since last summer, I've thought that the beginningGrace dialect should have a simpler list object, for which we throw efficiency to the winds, and reify all of the intermediate lists.

Would functional programmers use indexing for this problem? takeWhile seems like the idiomatic Haskell solution. We could easily add takeWhile to the Grace Enumerable type. What else is missing? A cleanup of collections is overdue, so this is a good time to suggest them.

Note that even if filter did produce an Indexable, the above solution would fail, because the sentinel with value -999 would be removed by the filter. I suggest that the way to write this functionally is

def goodData = url . takeWhile {e -> e ≠ -999} . filter {e -> e ≥ 0}
if goodData.isEmpty then { ... }
goodData.sum / goodData.size

Haskell lists are documented here. But if we want our libraries to be "simple", we will have to choose carefully.

I have resisted adding operations like sum to lists, useful as they are, because they require type constraints to specify when they can be applied, and we have never figured out who to handle type constraints. We probably also need a parametric way of getting the "zero" for a given object (like a Number) and operation (like +).

kjx commented 7 years ago

we should start by looking at Java8 streams. There's a book (for which I have a licence) mastering-lambdas-java-programming-in-a-multicore-world.pdf in Background Papers

kjx commented 6 years ago

As far as I can tell, a stream is a one-use iterable, while a collection is... a collection.

I got caught by a very odd bug: mapping an 'eval' across a list of parameters, turns out parameters were being evaluated multiple times, each time I accessed the mapped collection. When the parameters were blocks, things got truly weird..