The Artima Developer Community
Sponsored Link

Weblogs Forum
Getting Dynamic Productivity in a Static Language

78 replies on 6 pages. Most recent reply: Apr 5, 2009 3:19 PM by Cedric Beust

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 78 replies on 6 pages [ « | 1 2 3 4 5 6 | » ]
James Iry

Posts: 85
Nickname: jiry
Registered: Nov, 2007

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 9:31 AM
Reply to this message Reply
Advertisement
>
> Can you point out exactly what part is confused in the
> blog post?
>

Sorry, meant comment not blog post. My bad. Here's the confusion I'm referring to

>, while Structural Typing won't notice anything until you actually try to run it.

And here

> [regarding structural typing] It's the typical trade-off of statically versus dynamically typed languages. After about twenty years in this industry, I have a strong preference for an IDE or a compiler letting me know I screwed up as soon as I'm done typing, but your mileage may vary.

Both snippets seem to be conflating structural typing with dynamic typing. Structural typing will notice problems before you run it and your IDE/compiler can let you know you screwed up instantly.

Cedric Beust

Posts: 140
Nickname: cbeust
Registered: Feb, 2004

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 9:33 AM
Reply to this message Reply
> Wow, you have just jumped off the deep end.

Can you please avoid ad hominem attacks? It doesn't really help the discussion.

> When I do
> rename refactoring in Eclipse it lets me preview changes
> and decide if I want to go ahead.

The confirmation dialog doesn't take away the fact that the automatic refactoring is 100% guaranteed to preserve the semantics of the initial code. Whether you say yes or no, the code is guaranteed to work exactly as before.

This is very different from refactoring structural typing, where the programmer has to make a decision that could potentially break that code.

--
Cedric

James Iry

Posts: 85
Nickname: jiry
Registered: Nov, 2007

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 9:39 AM
Reply to this message Reply
> > So Java has refinements types! You just can't name
> them
> > so they're practically useless.
>
> I beg to differ. I use them quite often, typically to
> pass a listener:
>
> window.addListener(new WindowListener() {
> public void onClick() { ... }
> })

Oh, come on Cedric...read my comment explaining refinement types again. What you just wrote is not a refinement type because you have not refined the type of WindowListener, you've just specified an implementation of an anonymous subclass.

A refinement of the type would be adding a public method to your anonymous class, using a covariant return type, or otherwise REFINING the TYPE.

Type != class. Let me say that again. Type does not mean class. It does not mean implementation. It does not mean representation. It does not mean runtime tag. A type is a constraint used by the type checker to prove certain forms of consistency.

Java has refinement types because you can write

new WindowListener {
public void onClick() {//...}
public void damnImRefined() {...}
}.damnImRefined()

But they are practically useless because that refinement gets lost as soon as you try to pass it to another method, return it from a method, or assign it to a variable.

James Iry

Posts: 85
Nickname: jiry
Registered: Nov, 2007

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 9:42 AM
Reply to this message Reply
> This is very different from refactoring structural typing,
> where the programmer has to make a decision that could
> potentially break that code.

Give me an example of where it must break code that does not have an equivalent breakage with nominative typing. Don't just make broad claims, back them up.

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 9:45 AM
Reply to this message Reply
> > I don't know, Martin, I think from the user's
> perspective
> > these things feel like separate features, even if they
> may
> > be generalizations of each other. You mention that
> member
> > types can encode generics, so is your feeling is that
> > generics can be considered another representation for
> the
> > one feature of member types? I think to users they feel
> > like two features, because they have to learn two
> > different syntaxes and sets of rules. They feel quite
> > different.
>
> This is a very important point.
>
> Martin, I think your intimate knowledge of both the design
> and implementation of Scala might cause you to miss a few
> aspects of how users perceive the language.
>
> Whether you got structural typing for free or whether it
> made a lot of sense from a continuity or implementation
> standpoint, it's an additional syntax for users to learn,
> and in my experience, it adds little, is rarely needed and
> it should probably not have been added.
>
> Just a personal opinion based on my experience.
>
Well I think you're advocating for an 80/20 design, which is fine, but frankly I don't think structural types add much conceptual complexity to Scala. It is one more thing for users to learn, yes, but to learn all of the "features" of the type system is quite doable by professional programmers. And there's a tradeoff in that if you leave a feature out of the language that isn't needed very often, then that indeed makes the language simpler, but it makes your life more complicated when you actually want to do something that could benefit from that feature. That's the tradeoff. My comment before was that I've found Scala goes ahead and includes some features that aren't needed that often, but are needed sometimes, and makes the language a more complete toolset. When you need a particular tool, even if its one you don't need every day, in Scala you can just reach up and grab it and solve your problem easily.

Cedric Beust

Posts: 140
Nickname: cbeust
Registered: Feb, 2004

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 9:48 AM
Reply to this message Reply
> Oh, come on Cedric...read my comment explaining refinement
> types again. What you just wrote is not a refinement type
> because you have not refined the type of WindowListener,
> you've just specified an implementation of an anonymous
> subclass.

My bad, I read too fast. You are right that this kind of refinement is pretty much useless in Java since it can't be named and that Java does't support this kind of typing.

I'm wondering how common this need is, though, and if you really gain that much with this feature, compared to creating a new class/interface/trait...

--
Cedric

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 9:49 AM
Reply to this message Reply
> > Exactly: "ask if I want it to". Hence my claim that
> it
> > can't be done *automatically*.
>
> Wow, you have just jumped off the deep end. When I do
> rename refactoring in Eclipse it lets me preview changes
> and decide if I want to go ahead.
>
> Okay, so if you want a very narrow definition of automatic
> let me rephrase - the refactorer could automatically write
> an adapter with no attempt to fix up renames. Ta da, the
> impossible just made easy! Thank you, good night, you've
> been a lovely audience.
>
I think the difference that Cedric is getting at is that with a nominal type, if you change a method name say, it is defined what other things should change in your codebase such that the process can be completely automated. But if you change a method name in a structural type, it isn't necessarily defined what all changes should happen in the codebase, so you really do need to ask.

Cedric Beust

Posts: 140
Nickname: cbeust
Registered: Feb, 2004

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 10:03 AM
Reply to this message Reply
> > This is very different from refactoring structural
> typing,
> > where the programmer has to make a decision that could
> > potentially break that code.
>
> Give me an example of where it must break code that does
> not have an equivalent breakage with nominative typing.
> Don't just make broad claims, back them up.
Sure, consider:

class A {
def close() : Unit
}

class B {
def close() : Unit
}

def f(a : {def close}) { // (*)
a.close()
}

f(new A())
f(new B())

You then rename close() to finish() in B:

class B {
def finish() : Unit
}

At this point, the IDE is going to need some serious help from the programmer to decide how to apply this refactoring.

Should the code in (*) be touched?

If you replace "close" with "finish" there, then all the invocations of f() need to modify the type of their parameter as well, which means you need to rename "close" with "finish" in A as well.

If you decide not to touch the code in (*), then all the call sites that pass a parameter of type B no longer compile and need to be adjusted.

The root of the problem here is that while you thought that you were decoupling your code from types by using structural typing in the signature of f, you actually coupled together *all* the classes that are passed as parameters to this method.

While the classes A and B have nothing in common, the mere action of using a structural typing to reference the methods they have in common is coupling them tightly.

Structural typing has very deep consequences on your code base but very few developers will realize this until they are confronted with the kind of problem I just described.

I think it's much safer to force the developers to clarify the semantics upfront and to demand from them that they specify which type exactly the method f() should accept. It' a little extra work upfront but it will pay off big time down the line because the code will be clearer and, most of all, be automatically refactorable.

--
Cedric

Cedric Beust

Posts: 140
Nickname: cbeust
Registered: Feb, 2004

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 10:15 AM
Reply to this message Reply
Oh, and to address the second part of your question:

> Give me an example of where it must break code that does
> not have an equivalent breakage with nominative typing.

A better solution is to force the user to think ahead of time.

They need to first define an interface/trait Closeable:

interface Closeable {
void close();
}

and then, they have to decide which of the classes A and B implement that interface.

Once that decision is made, when they decide to rename B#close to B#finish, the decision path is clear: B is no longer a Closeable and therefore, f() should not be allowed to act on it. I think you can see the rest of the solution for yourself.

Structural typing suffers from the same problem as dynamic typing in that respect: it allows you to be lazy to get your code written faster initially, but eventually, the technical debt catches up with you and you will be confronted with very tough questions such as the one I described in the example above.

Static typing forces you to more upfront clarity: you need to think ahead what your types and classes are and how they relate to each other. It takes a little bit more effort, but in my experience, the gains in the long run make up for it.

--
Cedric

James Iry

Posts: 85
Nickname: jiry
Registered: Nov, 2007

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 10:20 AM
Reply to this message Reply
> > Give me an example of where it must break code that
> does
> > not have an equivalent breakage with nominative typing.
> > Don't just make broad claims, back them up.
> Sure, consider:
>
> class A {
> def close() : Unit
> }
>
> class B {
> def close() : Unit
> }
>
> def f(a : {def close}) { // (*)
> a.close()
> }
>
> f(new A())
> f(new B())
>
> You then rename close() to finish() in B:
>
> class B {
> def finish() : Unit
> }
>
> At this point, the IDE is going to need some serious help
> from the programmer to decide how to apply this
> refactoring.
>
> Should the code in (*) be touched?

First, that's easy to fix with an automatically generated adapter.

But I want to remind you of the challenge. You were to find a refactoring that doesn't have an equivalent breakage in nominative typing.

trait Closeable {
def close : Unit;
}

trait Foo {
def close : Unit;
}

class A extends Foo with Closeable {
def close() : Unit = // ...
}

class B extends Foo {
def close() : Unit = // ...
}

class C extends Closeable {
def close() : Unit = // ...
}



Now, we refactor the Closable trait
trait Closeable {
def finish : Unit
}


So what do we do? If we fix up A, it doesn't conform to Foo. So we have to fix Foo. But now B and C don't conform so they need to be fixed. And poor B had nothing to do with Closeable.

That's exactly analogous to your rename refactoring of a structural type either requiring sweeping changes that need programmer help.

And if you think this example is contrived then let me assure you I've seen it in actual production code.

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 1, 2009 7:08 PM
Reply to this message Reply
Hi James,

>
trait Closeable {
> def close : Unit;
> }
>
> trait Foo {
> def close : Unit;
> }
>
> class A extends Foo with Closeable {
> def close() : Unit = // ...
> }
>
> class B extends Foo {
> def close() : Unit = // ...
> }
>
> class C extends Closeable {
> def close() : Unit = // ...
> }

>
>
> Now, we refactor the Closable trait
>
trait Closeable {
> def finish : Unit
> }

>
> So what do we do? If we fix up A, it doesn't conform to
> Foo. So we have to fix Foo. But now B and C don't
> conform so they need to be fixed. And poor B had nothing
> to do with Closeable.
>
I had this same scenario pop into my mind last night before bed, but didn't want to get up to post it. This is indeed a case where it isn't defined what should be done when changing a method name in a nominal type, and the user will have to come in and make a decision. So I take back what I said before about it always being defined how to change the rest of the codebase when changing a method name in a nominal type.

But I still see a difference, in that this is always the case with structural typing. I think the root cause is that structural typing, like duck typing, doesn't involve a semantic contract that is implied by a nominal type. Basically by implementing trait Closeable, in class C, I'm saying that C is a Closeable, and must therefore fulfill its contract. So if I change a name of a method in Closeable, then I need to change that method name in C. It is defined that I would do that. (What isn't defined in your example, is what to do about the close method inherited from Foo.)

But with a structural type, say { def close: Unit }, just because I happen to be one of those today doesn't mean I have declared I will always be one. There's no contract in the structural type, just the signature. So if Cedric changes that to { def finish: Unit }, well it is not at all defined what to do with all the types that previously fit the { def close: Unit } structure. In fact I wouldn't even call it changing the { def close: Unit } type. { def finish: Unit } is just a different structural type, period. So you have to ask the user, and basically what's nice about structural typing compared to duck typing is when such refactors happen, the compile fails and you get a do list of places to look.

The other thought I had about structural subtyping, though, is that you wouldn't usually be changing a method name in a structural type, even though this is common in nominal types. That's because the name of the method is a big part of the structure being discussed in a structural type. For example, the two places I used structural typing so far were to make it easy to check for length or size on any object that has an appropriate structure. So there's no point to me changing the method name in the structural type. What instead will happen is the kind of refactor I did to the GoldenGateBridge object in the video. I change a method name in a nominal type, which makes it no longer a structural type that it previously was being used as. For example, if you're using GoldenGateBridge with a structural type in a test to be able to say:

ggBridge should have length (2200)

Then if you refactor GoldenGateBridge so that instead of a length method it has a span method, you'll now get a compiler error, and would likely want to change this line of code to:

ggBridge.span should equal (2200)

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 2, 2009 7:42 AM
Reply to this message Reply
> Has there been actual scientific studies that show what
> the productivity benefit is in practice? i.e. not just
> impressionistic viewpoints?
>
That's a very good question, and unfortunately I think the answer is not much. There have been some productivity studies here and there I believe, but the ones I'd heard about had to do more with techniques, such as pair programming. Mostly what we have to go on is anecdotes.

But my feeling anyway is that language makes a rather minor contribution to overall productivity. So many other variables come into play. The main one is the talent of the people involved. Another big one is the architecture. If the architecture anticipates a certain kind of change, then you'll be able to do that quite quickly and safely. But if not, then it can take a lot more work. Another is processes.

Tools matter too, and the programming language is a tool. What I've observed throughout my programming career is that as new languages become popular in the mainstream, the level of abstraction at which programmers work rises. I started out in assembly, then C, then C++, then Java, and now Scala. At each step I was able to work at a higher level in one way or another. And I think the reason is that being able to work at a higher level makes programmers more productive.

Martin Odersky

Posts: 84
Nickname: modersky
Registered: Sep, 2003

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 2, 2009 7:49 AM
Reply to this message Reply
> > This discussion reflects a general beef I have with the
> > way Scala is presented. People say it has features A, B,
> C
> > and so on, until readers feel that it's really a very
> > large language. It's not. The point is that Scala's
> > features are really very general. The same feature can
> be
> > interpreted in many different ways. So people who don't
> > know Scala come away with a wrong impression, that it
> has
> > much more different features than really is the case.
> >
> I don't know, Martin, I think from the user's perspective
> these things feel like separate features, even if they may
> be generalizations of each other. You mention that member
> types can encode generics, so is your feeling is that
> generics can be considered another representation for the
> one feature of member types? I think to users they feel
> like two features, because they have to learn two
> different syntaxes and sets of rules. They feel quite
> different.

They do. It's more that once you know the language better you realize that they are just different facets of the same core. Well, maybe not generics vs abstract types; we have not quite succeeded in unifying them completely (yet).
But a lot of the other features such as structural types are just facets of more general construct. The problem is that beginning programmers see more the facets than the core, so come away with the feeling that there are lots of different things in Scala. Nevertheless, there's a payback even for beginning users: Because the facets come from a common core, there's a much higher probability that they will work well together, than if they had each been designed in individually.

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 2, 2009 8:16 AM
Reply to this message Reply
> >> Yes, in the video I talk also about the productivity
> >> advantages of static languages. But don't forget there
>
> >> are some other advantages of dynamic languages too that
>
> >> I didn't cover. The three main missing ones to me are:
>
>
> >> 1. Conceptual simplicity. The language itself is easier
>
> >> to understand when you have a model of objects send
> >> each other messages, and at runtime if the message is
> >> handled it works, else you get an exception. That's the
>
> >> case in dynamic languages. In static languages, you
> >> have more complexity in terms of a type system to
> >> understand.
>
> I'm not sure there is any meat in that statement. If the
> static language makes intelligent use of type inference,
> the type system is not in the way. In fact the type system
> actually *helps*. Consider a method declaration. Without
> types on the parameters how do you know what's ok to pass
> without having knowledge of the implementation? A type can
> be the best form of documentation. And I won't get into
> the *huge* benefit of code completion, refactoring, etc.
> etc.
>
I had quite a bit of push back on this one from several people, so it sent me back to the thinking board. But I think that people's objections were mostly talking about how their lives could be complicated by dynamic typing. I really wasn't trying to say that your life would be simpler if you use a dynamic language, but more that the methods as messages model is simpler to understand than static typing, because with static typing you end up eventually needing to talk about type parameters and variance. To be specific, it is easier to understand Python's dynamic method invocation model than it is to understand Scala's type system. There are far fewer moving parts. But in exchange certain kinds of refactorings can be simpler in Scala than Python, figuring out the types of method parameters can be simpler in Scala than Python, and so on. So even though dynamic typing may complicate your life in certain ways compared to static typing, I think you have to still give dynamic typing credit for being easier to learn and understand than a static type system.

So that's one thing, but there was another aspect to this that I didn't bring up. I've had many conversations with dynamic typing enthusiasts whose opinions I highly respect, and one theme I've heard is that they don't like the feeling of "fighting the compiler." To give a specific example, I had a conversation with "Smalltalk" Dave Thomas. This is not Pragmatic Dave Thomas, though he's also a "dynamic" personality whose opinion I respect. PragDave is more a Ruby fan, and Smalltalk Dave is, you guessed it, a Smalltalk fan. He led the team that wrote Visual Age for Java in Smalltalk at IBM, and he said they were highly productive using Smalltalk to implement a Java IDE. But that Sun "wouldn't give them the coffee cup," so they had to do something in Java, and that became Eclipse.

So that was the history, and so I was asking him what it was that made him feel so productive in Smalltalk, and one of the things he said is he didn't have to spend time thinking about types. The implication being that you could move faster with this simpler model of message passing. I absorbed this and as I was working on ScalaTest later, I paid attention and tried to notice when I was spending time on types that probably wouldn't be necessary if I were programming in a dynamic language, and it did happen from time to time.

Back in my conversation with Dave, I said that a lot of static typing folks have an attitude that dynamic typing is good for prototypes, because when you're trying to explore,... I was going to say when you're trying to explore, you don't want to get slowed down or stuck trying to fix a type error that may be tangential to what you're trying to accomplish at the moment. But he interrupted me at that point and said, No when you're trying to think!

Some of the thinking about types I had to do when doing ScalaTest I felt was an investment on my part that will make life simpler for users of ScalaTest. But some of it did seem rather tangential to what I was really trying to accomplish. I feel like I'm fighting the compiler far less in Scala than Java, but sometimes I get the feeling that I'm experiencing in Scala those speed bumps that these dynamic guys complain about.

So that's the other aspect of "conceptual simplicity." One aspect is, easier to learn and understand the language. But the other is, easier to build stuff without hitting speed bumps all the time. I'm curious what other people's opinions are of the speed bump complaint.

James Iry

Posts: 85
Nickname: jiry
Registered: Nov, 2007

Re: Getting Dynamic Productivity in a Static Language Posted: Apr 2, 2009 8:37 AM
Reply to this message Reply
> But I still see a difference, in that this is always the
> case with structural typing. I think the root cause is
> that structural typing, like duck typing, doesn't involve
> a semantic contract that is implied by a nominal type.


Not so. Consider

trait Something {
/** Implementations of this method should not murder kittens */
def doIt : Unit
}

vs

/** @arg x: the doIt method should not murder kittens */
def foo(x : {def doIt : Unit}) = ...

Both have a semantic restriction that the user is responsible for following. And both are just as easy to break

class Evil extends Something {
def doIt = murderKittens
}

foo(new Evil)

* Note, no actual kittens were harmed during the writing of this message

Flat View: This topic has 78 replies on 6 pages [ « | 1  2  3  4  5  6 | » ]
Topic: AntiSocial Networking Previous Topic   Next Topic Topic: Mixins considered harmful/3

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use