Hacker News new | comments | ask | show | jobs | submit login

Can someone explain the dichotomy between lambdas-as-objects and whatever the alternative is? If they aren't objects, would they be a primitive value with no methods or superclass? What is the advantage of that?

EDIT: Ok, I think I'm starting to understand the issue. If lambdas are objects then they have to be typed by their signature. It would have to be a parameterized type i.e. generic. And because of type erasure, the call signature of lambdas would only be known at compile time. So you can't check, at runtime, that a lambda has a compatible signature. That does seem pretty hairy to me.

And if they aren't objects, I guess their signatures would be tracked with some sort of new parallel type system that could be better tailored for the purpose. Is that it?




Lambdas as objects vs lambdas as higher order functions is primarily about state. Lambdas make poor objects, especially in the absence of pattern matching; as soon as your API needs more than one method signature, you need to overload things, and you can get caught up in reimplementing the equivalent of Smalltalk message-send. If you want to make an object, you're usually better off using an object (or anonymous class etc.) when the language provides it.

Using lambdas to parameterize algorithms gives you more power, and if the algorithm can assume that the lambda passed in is stateless, it can do things like memoize (cache) calls to it to further optimize the algorithm. Lambdas as higher order functions help you make more composable libraries, because often the glue needed to join one bit of code to another is just a transformation of the return value of one function to the parameter type of another. That's an ideal application for a lambda.

For example, take sorting a list of objects by a particular attribute. A sort routine can be written that takes in a comparison routine, and that comparison routine can do the work of selecting the attributes and comparing them. But chances are you're comparing things like numbers and strings; you'll have other library code that can do those comparisons. So what you actually want, most of the time, is just the bare minimum: a lambda that, given the object to be sorted, returns the attribute to sort by. That's how the Sort routine works in .NET's LINQ, for example: myList.Sort(x => x.Name) will return a sorted sequence of the contents of myList in the order of x.Name property or field. The lambda here is extremely simple and syntactically light, and that's what makes the pattern work. Above all, the lambda isn't really acting like an object.


To the best of my understanding, lambdas-as-objects would mean providing structural types, i.e. generic function types that are able to describe the structure of a lambda (arguments and result type). e.g. (a fictive syntax):

    Function0<Int> f = () => 5
f here is a lambda that take no args and returns an Int. It's type is Function0 (0 for no args) and parameterized with the return type.

Another example with 2 args:

    Function2<String, Float, Int> f = (String name, Float rate) => 5
The Function* types are the generic structural types able to describe lambdas. C# has them, and so does Scala.

Java however decided not to have them, because they don't adhere well with Java's history with nominative types (a type has a name that carries sense). They rather came up with the SAM auto conversion, which is a neat idea in itself, but a bit less flexible than structural types, and may (and will lead to a Cambrian explosion in the number of types, e.g. Reducer, Mapper, Folder, Filter, Predicate, etc.)

What's even worse though is erasure: Java generics are erased at compile time, and this fucks up things really bad with overloading, so in a code like this (in the type List for example):

    public <S> List<T> stupidOpName(Function1<T, S> f) { ... }

    public List<T> stupidOpName(Function1<T, Boolean> f) { ... }
After erasure, we'll end up with :

    public List stupidOpName(Function1 f) { ... }

    public List stupidOpName(Function1 f) { ... }
i.e. 2 methods with the same name and same signature. This is bad. You don't ever want that to happen. And fortunately the java compiler won't let it happen.

So, the alternative is I said SAM (Single Abstract Method) types: no structural types, but lambdas can be automatically converted to a SAM if compatible, so you could do this for example:

    Runnable r = () => { ... }
The lambda gets converted to the Runnable type.


> C# has them, and so does Scala.

With the slight difference that its generics are reified, so all `Function` generic arities have the same name (it's `Function<T>`, `Function<T1, T2>`, ...), although because there is no `void` or `Unit` type in C# it needs two base function types, to handle functions with a return value (`Function`) and functions with no return value (`Action`).

I believe the Kotlin project fixes this issue by having a first-class `Unit` type, so C#'s `Action` becomes `Func<Unit>` in Kotlin.


C#'s type system is a bit messy over there. Besides Func<T> and Action<T>, there's also Predicate<T> , which works the same as Func<T, bool> except for being an entirely different type that's non-trivial to cast to Func<T, bool>. And the whole lot look a lot like delegates.

If they were all just Func<T> (or aliases for that, or wrappers over that when multicast is really needed), it would be a lot simpler.


Even Java has the Void type, meaning you can write:

    Function<Void>
It's a bit shady though as you still have to return a value from the body of the method, and that value has to be null.


That sort of this is also easy to do in C# or any strongly typed language. Similar things in c# are the DBNull type which "Represents a nonexistent value" from a database (http://msdn.microsoft.com/en-us/library/system.dbnull.aspx ) and Type.Missing which "Represents a missing value in the Type information"


> It's a bit shady though

'bit of an understatement there.


Are lambdas always created as SAM types or do they have some other form?

I assume there is some way to specify the SAM type explicitly, for cases where it can't be inferred properly?


The answer to your first question is yes, a lambda is always assigned to a SAM type.

As to your second question, yes, you can cast a lambda to explicitly specify it's target SAM type:

    obj.method((Runnable)(()=> println("oh hai !")));


So, generic type erasure is still a bad idea.


Depends how much of your language happens at runtime. In very statically typed languages such as Haskell, type erasure works just fine (that's what GHC does), though it does give the impression of taking more namespace surface when you need generic type varargs (e.g. you can have `Foo<T>` and `Foo<T, U>` when your generic types are reified, if they're erased you'll need `Foo<T>` and `Foo1<T, U>`, there are two types in either case, but reified generics give the impression of a single type with a variable number of type arguments).

Erased generics are a bad idea when your type system is full of holes (easy to bypass) and lots happen at runtime, as is the case in Java.


Yes.

Haskell still needs to do some stuff at runtime for type classes.




Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: