ark-lang / ark

A compiled systems programming language written in Go using the LLVM framework
https://ark-lang.github.io/
MIT License
677 stars 47 forks source link

Remove Go style interfaces, add explicit interfaces #688

Closed raoulvdberge closed 8 years ago

raoulvdberge commented 8 years ago

This is the thing I hate most about Go.

IMO they:

felixangell commented 8 years ago

I agree with this to be honest, there are few things I dislike about Go, and the implicit interfaces are probably the biggest offender...

Then again in most cases I will opt for explicit over implicit meaning, I don't see any overly compelling reasons for implicit interfaces, the only advantage I can think of is that it's quicker to type. Though, as Raoul mentioned, when your code scales things get messier and you have to refer to documentation to understand things.

I found myself looking at the SSA implementation in the Go standard library, and had to look at the tables in the documentation, or manually ctrl+finding things in interfaces that were used in structures. Personally, I don't think you should have to do this to understand something, you should just be able to read it and it expresses itself clearly.

Just my two cents, I'm :+1: on this issue.

kiljacken commented 8 years ago

I would not be opposed to this.

On Wed, 10 Feb 2016 02:34 Felix Angell notifications@github.com wrote:

I agree with this to be honest, there are few things I dislike about Go, and the implicit interfaces are probably the biggest offender...

Then again in most cases I will opt for explicit over implicit meaning, I don't see any overly compelling reasons for implicit interfaces, the only advantage I can think of is that it's quicker to type. Though, as Raoul mentioned, when your code scales things get messier and you have to refer to documentation to understand things.

I found myself looking at the SSA implementation in the Go standard library, and had to look at the tables in the documentation, or manually ctrl+finding things in interfaces that were used in structures. Personally, I don't think you should have to do this to understand something, you should just be able to read it and it expresses itself clearly.

Just my two cents, I'm [image: :+1:] on this issue.

— Reply to this email directly or view it on GitHub https://github.com/ark-lang/ark/issues/688#issuecomment-182160941.

MovingtoMars commented 8 years ago

I'll write up a good rebuttal for this once I get the time (probably tomorrow).

vnev commented 8 years ago

+1 I like this change. Implicit interfaces was a big reason for me to dislike working with Go.

MovingtoMars commented 8 years ago

Why implicit interfaces are good:

@raoulvdberge

are unclear, especially when the codebase grows, you won't have an overview of what inherits what;

This can be partially solved by having the ark doc tool list the interfaces from the module a type implements. Also, with tooling.

kiljacken commented 8 years ago

I'll upfront say that I don't really care anyways. I just want my generic interfaces.

Implicit interfaces promote using smaller interfaces and abstractions rather than big clunky ones. This promotes better decoupling and scaling.

This I don't really agree with. There is no inherent incentive to use smaller implicit interfaces and neither is there any incentive to use large explicit interfaces.

Makes refactoring much easier.

Could you perhaps give an example of this? I'm not really sure I see any scenarios where the implicit interfaces would be all that more refactoring friendly than the explicit ones.

In Rust, you can import a struct and not be able to access some of its methods because you need to import another module which contains a trait. (see http://stackoverflow.com/questions/25298953/do-i-have-to-use-a-trait-in-order-to-call-methods-defined-in-that-trait)

This one is a moot point related to how rust implements things. We could easily allow access to the methods without having to use the implicit interface.

This can be partially solved by having the ark doc tool list the interfaces from the module a type implements. Also, with tooling.

The problem here is that you'd have to traverse all the modules you use and list all interfaces this type could possibly implement. This would obviously be a bad fit for the docs, but I agree it could be done via a proper set of tooling.

felixangell commented 8 years ago

Duck typing. If something has a String() string method, it automatically matches the fmt.Stringer interface without you having to tell it.

I think this is the only notable argument for duck typing, it's a trade-off for having explicit code that does what it says on the tin, or for having code that's in some way is more scalable, and convenient to write. With regards to Ark, it makes more sense to be explicit. Go was intended as a server side language for Google. Scalability and ease of use matter a lot in this context, and I don't think this particularly suits Ark.

"Moreover, you don't have to work them all out ahead of time; the whole design can evolve without invalidating early decisions." - Rob Pike

Implicit interfaces promote using smaller interfaces and abstractions rather than big clunky ones. This promotes better decoupling and scaling.

"Go's interfaces tend to promote small and cohesive behavior" - http://openmymind.net/Things-I-Wish-Someone-Had-Told-Me-About-Go/

I'm not sure about this. I personally find a lot of the "philosophical" reasoning behind features like this (or similar) are usually very broad and subjective. I imagine a lot of people won't really think about it much and would usually opt for the laziest thing they could do. To me this is something where you could easily program terrible spaghetti code, or it becomes over engineered -- like a double edged sword or whatever the phrase is.

Makes refactoring much easier.

Again, this is the trade-off for convenience I guess.

raoulvdberge commented 8 years ago

Guess we will never agree on this.

kiljacken commented 8 years ago

Well, we've yet to see Liam's response on any of these, if he wants to respond that is. So I believe we should at least wait for that, or him saying that he has nothing more to say lol.

felixangell commented 8 years ago

:+1: It looks like a lot of us aren't really fond of implicit interfaces... I don't mind too much but I think that having something like Rusts traits would be more fitting for the languages purpose

kiljacken commented 8 years ago

The one case where I could consider an real difference would be in the cases where you say: "I want to explicitly implement this interface for this type always and yell at me if I don't." We might want a way to handle that.

The rest of the "benefits" from explicit interfaces can be gained via appropriate tooling imo

felixangell commented 8 years ago

The one case where I could consider an real difference would be in the cases where you say: "I want to explicitly implement this interface for this type always and yell at me if I don't."

Yeah completely agree here, this is something we want even if we keep implicit interfaces.

The rest of the "benefits" from explicit interfaces can be gained via appropriate tooling imo

Yeah, I don't mind too much about this -- though I feel that we have to have tooling to make programming easier seems weird to me.

kiljacken commented 8 years ago

though I feel that we have to have tooling to make programming easier seems weird to me

But that's exactly what tooling is for. You can't have your language do everything. In this case we would just be exchanging annoyances, and I feel like the annoyances from implicit interfaces are more easily assisted with good tooling, that those introduced by explicit interfaces.

felixangell commented 8 years ago

@kiljacken I understand that, but I mean for a syntax feature in language. I can understand tooling like a debugger or something since I don't have to use it to write code that works, though it definitely helps.

Kind of like a package manager, I shouldn't have to be married one just to write code in that language, and I shouldn't have to use one at all if I don't want to.

kiljacken commented 8 years ago

@0xbadb002 You don't have to use it. You don't usually need to know all the interfaces a certain thing implements, and in the case where you're checking against a specific interface you won't need a tool.

If you however want to know all the interfaces in the entire codebase that it implements you'd have to use a tool.

felixangell commented 8 years ago

@kiljacken It just feels wrong for a syntactical feature of the language.

That said, I'm not saying don't do it! lol

kiljacken commented 8 years ago

@0xbadb002 It's an unreasonable task to expect a syntactical feature to fulfill IMO. The amount of times during a projects development where you'd need such a tool tends to zero.

You wouldn't want a syntactical feature that renames all usages of a certain term in your codebase either. At some point a task just becomes so expansive that it's unreasonable to expect the language syntax itself to fulfill it.

felixangell commented 8 years ago

@kiljacken I don't mean like that. I mean to understand the syntax of a language I shouldn't need tooling. Obviously, this is a personal opinion - but I don't like being glued to tooling to understand the intent of code, it should express itself clearly.

Though I am arguing against tooling, if we're going for implicit interfaces then yeah we make a tool to make this easier, but I personally dislike the idea. It seems a little dirty to me.

kiljacken commented 8 years ago

@0xbadb002 Assuming that you meant semantics and not syntax, I get completely get your point, although I do not think it's as severe as you think it is. Give me a bit of time and I'll write up something to help us start finding a compromise that would serve all of us.

felixangell commented 8 years ago

@kiljacken Yeah I guess, when I say syntax I mean that the syntax should express what something does, and if it doesn't to the point that I have to use tools to figure out what it means, then it feels dirty.

And cool, ping me when you got something :+1:

kiljacken commented 8 years ago

@ark-lang/owners

So at the current point it appears that we are 2/3 people for the move to explicit interfaces, 1 person for the stay with implicit interfaces, and 1/2 person indifferent. For the sake of a clean resolution I will for the context of this writeup disregard that in it's entirety.

Liam had a few key points as to why he'd like to stay with implicit interfaces. The following are these points interlaced with a summation of the commentary on each point:

  • Duck typing. If something has a String() string method, it automatically matches the fmt.Stringer interface without you having to tell it.

This point I acknowledge. You wouldn't quite get he same easy of use, but a sane set of runtime specified traits could alliviate much of the pain.

  • It makes interface composition easy. For example, interface Thing { fmt.Stringer, io.Writer }. Even if a type implements fmt.Stringer and io.Writer, if interfaces are explicit it won't implement Thing. So if this is for a library you don't control the source code for, you're SOL.

Another point I acknowledge. I do however believe it could be solved with a hybrid solution. If a trait is only a composition of other traits, it would be conceivable to handle this implicitly in the compiler.

  • "Moreover, you don't have to work them all out ahead of time; the whole design can evolve without invalidating early decisions." - Rob Pike

We'll start by ignoring the fact that this would normally be considered bad development practice. I could see there being some friction here. However, if you write your codebase using the exact same method names and signatures, such that you could easily switch to an implicit interface, you might as well have created an explicit interface at the start of it. And in the case where you do not have perfectly matching names and signatures, there would not be too much additional friction to creating an explicit interface.

  • You can write an interface for some code which you don't control the source code for.

This is an artificial limitation. From a compiler standpoint there's no good reason to disallow implementing a trait on a type not defined in this module. The main struggle would be how to handle accessing internal behavior, although this would be a problem with defining an implicit interface as well.

  • Implicit interfaces promote using smaller interfaces and abstractions rather than big clunky ones. This promotes better decoupling and scaling.

There is no inherent incentive/disincentive from either implicit nor explicit interfaces for the design of smaller interfaces. This is purely a problem of language culture.

  • Makes refactoring much easier.

I fail to see in which scenarios implicit interfaces would prove much of an improvement over explicit interfaces in terms of ease of refactoring.

This seems to be a duplicate of the second point.

This is another unnecesary compiler limitation. There is no inherent reason why the compiler would need to force the user to use it explicitly.

Roundup

So following this the main points of contention seems to be:

The first one is a legitimate concern that could cause some friction. I do however belive that a smart selection of default traits that would be available via the runtime would alliviate this friction.

The second could be solved by a hybrid solution, allowing pure compositions of explicit interfaces to be implicitly matched if the type implements the composited interfaces.

Furthermore I believe that a lot of pain could be relieved by implementing a lighter weight syntax for specifying one method traits.

kiljacken commented 8 years ago

I do however belive that a smart selection of default traits that would be available via the runtime would alliviate this friction.

If this paragraph confused anybody what I mean is (thank for asking, Felix):

We (as in us the authors, not the complier) smartly select an amount of traits that would be useful to have available straight in the runtime.

kiljacken commented 8 years ago

allowing pure compositions of explicit interfaces to be implicitly matched if the type implements the composited interfaces.

Another explanation (thanks again, Felix):

Say you have an interface type ReadWriter interface { Reader, Write } that doesn't have any methods in and of itself. We could match this implicitly if you explicitly implement Reader and Writer. That is, you do not have to explicitly say that you implement ReadWriter if you implement both Reader and Writer.

MovingtoMars commented 8 years ago

Before we discuss this further, what is the actual proposal for how explicit interfaces would work?

kiljacken commented 8 years ago

I'm imagining something ala how rust traits are specified. I do not know if this is what @raoulvdberge is looking for though.

raoulvdberge commented 8 years ago

I don't really care, as long as it's explicit. Though I do like Rust traits...

MovingtoMars commented 8 years ago

I don't see how this wouldn't be solved by docs: Have the docs list every satisfied interface fromn the package itself, and all packages it uses.

MovingtoMars commented 8 years ago

I'm away until Friday, so don't just go and remove them while I'm gone.

kiljacken commented 8 years ago

@MovingtoMars We wouldn't. As I've said, I see no real reason to do the switch, so unless there's some game breaking argument I won't even consider it.

felixangell commented 8 years ago

@kiljacken I can't say I'm particularly bothered either