Posted: Mar 11, 2012 8:15 pm
by mizvekov
VazScep wrote:Yes. They effectively appear in Java as well, when you have a class with a single method such as "doIt".

There is a limited correspondence between lambdas and classes. Just as lambdas can be classes with syntax sugar, a lot of classes can be records of lambdas. For instance, in Standard ML, I could fake the implementation of a Counter class which keeps track of a value that can be reset as follows:

Code: Select all
let new_counter reset =
  let m_x      = ref reset in
  let incr ()  = m_x := !m_x + 1 in
  let reset () = m_x := reset in
  let get ()   = !m_x in
  (incr, reset, get)

let counter_incr (incr,_,_)   = incr ()
let counter_reset (_,reset,_) = reset ()
let counter_get (_,_,get)     = get ()

The ugliness of tuples and those last three functions is usually buried under the syntax sugar of records (amusingly, a number of Lisps implement records as nothing more than a bunch of macros to write exactly this sort of code).

You can even have encapsulation based on which values you include in the returned tuple: here, I leave m_x as a "private" value, since it cannot be accessed outside the function.

Cool. c++ has tuples now, with std::tuples. But they are "disconnected" from ordinary struct types which have basically the same form. boost Fusion is something that proposes to fix this, so you can declare an ordinary class and "map" it into an equivalent tuple, so you can mix the two forms freely.

VazScep wrote:
The only thing I think that is significantly missing from this picture is subtyping. And for this reason, Ocaml has its own object system, and supports subtyping in a manner which is definitely worth a look. The idea is that the subtyping is structural rather than nominal. In virtually all languages with subtyping, including C++, the type hierarchy is a directed graph which is explicitly coded by the programmer by associating each class with its named supertypes. In Ocaml, one class is a subtype of another just in case it supports all of the other's methods (this is sometimes called "duck-typing" by Python programmers, despite Python not having a type-system). Moreover, structural types can be anonymous using so-called "row-polymorphism."

Again, very cool. I suppose you have duck typing in C++ when you use template parameters. But there is no way to specify up front what characteristics a type must have to be acceptable, so it stays implicit in the code. When you pass the wrong type, the error happens inside the template definition, sometimes many layers deep, instead of at the point of instantiation.

VazScep wrote:
Suppose I define a function foo which takes an x and invokes its bar and baz methods as follows (# accesses instance methods):

Code: Select all
let foo x = x#bar + List.length x#baz


Then Ocaml will infer the type of foo as:

Code: Select all
val foo : < bar : int; baz : 'a list; .. > -> int = <fun>

This says that for any type 'a, foo is a function which takes some object supporting both a "bar : int" method and a "baz : 'a list" method, and returns an int. It can now be used with any instance of a class which happens to support these methods, even if there is no explicit subtyping involved.

That's a capability missing from the c++ new standard. One of the reasons it's infamous for it's incomprehensible error messages is because, like I said, there is no way to specify restrictions on the types that can be passed as template parameters. So usually when you pass a nonsensical type, the error happens in implementation dependent code, sometimes many layers deep.
A solution to this almost passed on c++11, something called concepts, but it was pulled out in the last minute (figuratively) because one issue popped up and they did not want it to delay the whole thing. I think it will be up again in five years.

VazScep wrote:Okay. I hadn't actually noticed this, but yes, it'd have hoped there would be a mechanism which did the variable capture automatically.

On this matter, this is where I still feel C++ has got things right compared with nearly every other OO language: having a copy-semantics, a reference-semantics and a const keyword, so that reasoning about mutability isn't such a nightmare.

Yes. The new standard also defines a move semantics, and so you can declare a special kind of constructor, called a move constructor, which can "rob" another instance's internal data instead of copying it as in a copy constructor. That way it can make it pretty inexpensive to return complex data types, like std::vector, by value, where in the old standard there would be a superfluous copy of allocated data followed by the original being deleted.

VazScep wrote:
Here, I'm guessing that if you want a lambda to capture a variable that will be mutated from outside, and you want the lambda to see the update, you would generally use capture by reference?

Yes.
VazScep wrote:
Other times, you might want the lambda to have its own copy, and you would use mutable (for primitive types?) and use capture by value otherwise?

Well capture by value/reference and mutable are orthogonal things. If you want the lambda to have it's own copy AND want to be able to modify it, then you would capture by value and declare the lambda as mutable. Likewise, if you want to see external modifications to that variable, AND also be able to modify that external variable, you would capture by reference and declare the lambda mutable.

In short, if you want to see external updates, you capture by reference, otherwise by value.
If you want to be able to modify that captured reference / value, then you declare it mutable.

VazScep wrote:Yes, my C++ knowledge is very rusty. I'd even forgotten entirely about initialisation lists, but suddenly remembered them when I saw the code!

Heh, same thing about Haskell here. I had learned some of it about a year ago, but had no activity with it, so something started fading away from my memory already.

By the way, I have been playing with that example you sent me, but I am a bit confused about somethings.

Firstly, what are called "axioms" in the code are actually axiom schemes, right?
So for example, I can use that code to do things like this:
Code: Select all
*Propositional> let t1 = inst theoremTerm (Theorem (Var axiom1 :=>: Var axiom3))
*Propositional> let t2 = inst theoremTerm (Theorem (Var axiom1))
*Propositional> mp t1 t2
[Theorem ((Not (Var "p") :=>: Not (Var "q")) :=>: (Var "q" :=>: Var "p"))]

Which is alright and dandy, unless I am using that thing not the intended way, but I can't actually see how that can be used to represent a proof of a propositional logic theorem.