The Strange Loop conference
Peter Alexander
peter.alexander.au at gmail.com
Wed Sep 21 13:34:34 PDT 2011
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.
More information about the Digitalmars-d
mailing list