On 21/09/11 8:29 PM, Timon Gehr wrote:
Where D still loses when compared to Scala is functional code syntax:
val newcol = collection filter {x=>x<5} map {x=>2*x}
or maybe
val newcol = for(x<-collection if(x<5)) yield 2*x
vs
auto newrange = filter!((x){return x<5;})(map!((x){return 2*x;})(range));
Of course, you could write:
auto newrange = filter!"a<5"(map!"2*a"(range));
At least I think that works??
I believe that is part of the reason why it is less known. If I write
functional style code in D I unfortunately usually get feedback that it
is "extremely hard to read". Furthermore, Scala has built-in tuples, and
eg delimited continuations, which enable some more functional
programming idioms. std.algorithm/std.range are also considerably (and
as I understand, deliberately) underpowered for FP when compared to eg
Haskell's standard set of libraries, and writing a good range from
scratch is usually a serious undertaking.
The problem it's simply intractable to do lazy computation in D while
maintaining the 'correct' static typing.
For example, in Haskell, map (correctly) has the signature:
map :: (a -> b) -> [a] -> [b]
but in D, std.map has the signature (expressed in some Haskell/D pseudocode)
map :: (a -> b) -> [a] -> Map!((a -> b), [a])
To get the same kind of signature as Haskell, you'd need to use eager
computation in D, but that's horrendously inefficient in most cases.
Alternatively, you can use interfaces, but then you lose type
information in a lot of situations.
The problem with the D situation is that if I have a function:
void foo(int[])
then it can't be called with:
foo(map!"2*a"([1, 2, 3]));
I'm forced to either:
(a) use eager computation by wrapping it in array(...)
(b) change foo to be a template
What if foo is a virtual member function? Those can't be templates.
What if foo recursively maps to itself? e.g.
auto foo(Range)(Range r)
{
if (r.length == 1)
return r.front;
r.popFront();
return map!"2*a"(r);
}
Someone in D.learn tried to write quickSort in a similar way, but it
obviously doesn't work because of the infinite number of template
instantiations. To simulate lazy computation in D, you require a type
transformation, which doesn't work with recursion. You're only choice is
to abstract away to some sort of IRange!T interface to remove the type
divergence, but then you lose performance, and in some cases, type
information.
Of course, if every function that accepts a range becomes a template
then you have the practical problem of managing the number of template
instantiations. Code bloat is still a real problem with heavy template
usage.
Finally, map in Haskell just makes sense. You have a list of a's, a
function of 'a to b' and you get a list of b's out the other end. It's
simple, it's exactly what you want, and it just works. You can't say the
same in D.
Of course, what D's lazy ranges do give you is performance in the simple
cases*. There's no indirect function calls, which means things can be
inlined, and you save yourself a few potential cache misses. We just
have to accept that this performance is at the expense of simplicity and
expressability.
* I say simple cases because Haskell compiler can do deforestation
optimisations, which are essentially intractable in D due to its
imperative roots.