For what it's worth,
Sebastian, Simon Peyton Jones agrees with you:
http://research.microsoft.com/en-us/um/people/simonpj/papers/haskell-retrospective/
(Skip ahead to page 56, he talks specifically about its use in
intellisense, calling it "the power of the dot").
To me, there is another very important point regarding dot notation that
I didn't see mentioned yet: it enables name resolution on a per-type
basis. In Haskell, if I want to have to have something like
"foo.context()" and "bar.context()" where the context of a foo has one
type and bar has another, it's a big pain and I have to use import
qualified or something crazy like that. In OOP this is no issue. And I
find this very natural.
Anyway, I think the design of the trait system is very much going
towards a "best of both worlds" direction. You can define methods and
you can define standalone functions. Based on our last meeting, there
seems to be general agreement towards a system that allows methods to be
imported as standalone functions that take the receiver as first
argument as well [1]. Like most modern languages—including Haskell,
Java, and C#—we support both parametric polymorphism (bounded generics)
and ad-hoc polymorphism (trait types). Both have their place, after
all.
So basically there is no conflict here. You use the right tool for the
job.
Niko
[1] As initially proposed here:
https://mail.mozilla.org/pipermail/rust-dev/2012-August/002255.html
>
And, in case nobody has pointed them out yet, here are a couple more
resources:
> * An introduction to traits on Patrick's blog:
> * A talk I did in August, with a side-by-side Haskell/Rust
>
Cheers,
Thank you for the references Lindsey.
>
One minor non-semantic, non-scientific benefit of OOP syntax over
> Algol-style function application syntax is that it's
syntactically
> more composable—compare
`a.foo().bar().bas()`, which reads
> left-to-right and
composes to the right, to `bas(bar(foo(a)))`, which
> reads inside-out and requires bracketing on both sides of an
inner
> experssion to compose calls. (Of course this only
works if your APIs
> are designed up front in a "fluent"
way, and isn't quite as flexible
> as what you can do with combinators in ML or Haskell.)
I'm
a math guy so to me syntax `bas(bar(foo(a)))` is natural. Besides as
you noticed yourself this only work in certain circumstances. For
example when you have additional parameters, the syntax could sometime
be something like `c.bar(a.foo(b)).bas()` which is less readable in my
opinion than `bas(bar(c, foo(a, b)))`. But all this is very subjective,
isn't it ?
>
I think there are two other benefits. The first is intellisense/code
> completion. Type "foo." and get a list of all methods in scope with
a
> matching self type. This can be a massive productivity
boost as it
> basically puts all your code and
documentation in the IDE in a context
> sensitive way. Much harder to do this if you have to remember
the name
> of the method before you see anything.
I
agree that it's easier to start from a variable existing in the context
rather than from a namespace or from a function name. But completion
does not necessarily have to insert code after the considered
identifier, even if it is indeed more usual.
> Second, and much more subjectively, I think
there's a difference
> between sort of value oriented
programming, and the more low level
> mutable programming
type where you're dealing with "objects" that have
> identity and complicated invariants. Subjective, but tying the
methods
> to the data type in those cases "feels" okay to
me because they kinda
> are inseparable in practice, so
they might as well be syntactically
> tied together as well (with a more functional style you would
group
> them by modules and visibility, but syntactically
it doesn't look like
> an indivisible "object" that have a
small set hard-wired built in
> methods on it as opposed to a value and a random grab bag of
>
operations that happen to have matching types but could be written by
>
any old schmuck and not necessarily "part of the object").
This is precisely the subjective feeling I don't
agree with. This feeling only exists because you think of
processes/functions/methods as part of a single object rather than
interactions between a group of objects. This is a very "Aristotelician"
view. It sometimes work well (when object can be seen like "active"
autonomous entities) but not in the general case IMHO.
>
It does not make sense. Think about a general add interface. It's
>
type could be like the following (I am still fuzzy on rust's memory
> location/lifetime type modifiers, so here I assume T is
somehow
> copy-by-value):
>
>
add( a: T, b: T ) -> T
>
> There's nothing
special about a compared to b, and also the return
> type is the same type. If we could generalize this to an
interface
> called "Addable", then a single implementation
would exist for a given
> type, and there'd be nothing
special about which argument position(s)
> have that type.
Hem ... So this mean
we agree right ? My point was precisely that this does not make sense.
The syntax need not to give one of the arguments a specific role.
> The common OO idiom you allude to specifically ties
interface, data
> accessibility, and namespace together.
That's a particular choice.
> You are right that these can
be decoupled.
>
> I'm personally a fan of "fewer ways to do it;
less flexibility", so I
> like nailing down a namespace and
data accessibility for example. Or
> for another example,
I'm a fan of "the namespace must correlate
> directly to the file path in some manner" versus having those
>
decoupled.
I may only partly agree. I too
prefer Bertrand Meyer's "A programming language should provide one good
way of performing any operation of interest; it should avoid providing
two." to Larry Wall's "There's more than one way to do it". But this
only apply to perfectly equivalent contructs. If there is several way to
do related but distinct things, I found flexibility better.
> But that's just me, and I see more flexibility
in rust. I'm not
> certain but I think data accessibility
may be tied to namespaces
> (which are decoupled from
filenames somewhat), as well as types, so
> maybe it already has the flexibility you describe?
In
all the docs I've read until now, the word "private" only appeared in
the section for classes, so I guess no.
> This is intriguing, but not as general as
multi-parameter type
> classes. Consider an interface for
two types, A and B, like this:
>
> insert(
elem: A, container: B ) -> void;
> peek( container: B ) -> Option<A>;
>
>
There's no (A,B) parameter or return type anywhere. That would fit
>
into insert(), but it would not make sense for peek, where I have no B
> at hand.
This is definite the kind of
interface/trait I would like to be express in Rust. But apparently it
is not possible with the current design.
> Multiple parameter are very useful eg a collection and a
second type ,
> but it gets messy... it works ok provided
its in a single compilation
> unit but else you get
compilation unit dependency loops ,with generics
> ( which are similar) you can auto generate new libs to
handle this
> not sure about rust traits..
Hi
Ben. I'm not sure to understand the link with compilation loop. Any
previous discussion on this topic?
So in conclusion, this means multiple parameter
traits are not planned to be implemented in Rust ? right ?
--
Eddy Cizeron
Hello,
I'm
new to Rust, and I have some questions concerning some aspects of the
language. From what I understand until now, rust has no dynamic linkage
for methods as it would be the case in any standard OOP language. This
is surely an intentionaly different philosophy from the current trend
among other recent programming languages. But why not after all. I like
the idea. So I find myself skeptical when I read the chapters of the
documentation about OOP-like concepts like classes or interfaces.
The essence of the "class" concept lies in the
possibility to gather a data structure (a type) and the related
processes that can act on the corresponding instances (methods) in a
single bundle. But, tell me if you disagree, the only interesting
purpose of such an operation is the ability to use inheritance. If no
concept of inheritance exists, I hardly see what is the need of making
functions "pets" for data. All we obtain are classical design issues
like "is my 'f(a,b)' function a 'a.f(b)' method or a 'b.f(a)' method ?"
(and I don't think the rust "resource" concept requires classes, as
destructors or destructor-like functions are not methods). I'm afraid
that the answer is merely to offer a supposedly "attractive" syntax for
OOP users. And I think this would be a weak argument
The concept of interface/implementation in rust is
surely different than the usual one, but again to me it looks like it
has some limitations due to the same reason than above: it tries to look
OOP. I could have imagined a very similar interface concept but that
does not focus on a single type:
iface shape_drawing<Sf, Sh> {
fn draw(Sf, Sh);
fn bounding_box(Sh) ->
bounding_box;
}
fn draw_twice<Sf,
Sh with shape_drawing<Sf, Sh>>(surface: Sf, shape: Sh) {
draw(surface, shape);
draw(surface, shape);
}
Of
course this example is a simple one. And any consistent system like
this (if it does exist but I'm pretty sure it does) would have been more
complex than the current one. But also more general and then more
powerful.
Let me clear about something: I don't expect anybody
to change the current design. I'm nobody and I know it's always easier
to criticize than to do things. I'm just expressing the feeling that
while Rust tried to take a different direction from all the other new
JVM-compliant-OOP-languages-with-functional-features (which I really
really value very much) it seems it has not got rid of some conceptual
patterns that are not, IMHO, necessary anymore. And I wish people could
give their opinion about this.
Thank you
--
Eddy Cizeron