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

And on the flip side, interfaces allow heterogeneous lists; []io.Reader can have several different distinct types that implement io.Reader in them, whereas the contract version would be "a slice of a type that implements io.Reader" but it would have to all be the same type.

I won't deny they're not orthogonal, nor that we're probably going to see some confusion about which to use when from beginners, nor that there are probably interfaces in the wild that really ought to be contracts (I'm pretty sure I've got a couple myself, though I haven't fully checked until the proposal is stabilized), but neither do they quite stand in for each other. Perhaps if this was in the language from day one, more work would be put in to finding a way to make just one "interface/contract" feature with some kind of (probably confusing) parameter in it that lets it serve both purposes, but doing that today doesn't seem to be on the table.




Rust and Haskell both use something called existential types to implement their versions of Go's interfaces. An existential type is basically a type meaning "any type that implements the given type-class/trait/concept", so you can use to create an array of different types that all satisfy the same contract. Type-classes and traits are roughly Haskell and Rust's equivalents of Go's concepts. Interestingly, Go contracts actually support a feature missing from Rust: multi-param contracts; contracts that refer to two or more types.


... and Rust does unify both of these use cases with traits, that is, what you're referring to is a "trait object", which is the same thing as Go with regards to interfaces. The difference is when you use a trait to bound a generic, in which case they get monomorphized and become "direct calls" in the OP's parlance.

I haven't re-read it recently, but https://softwareengineering.stackexchange.com/questions/2472... did contain a pretty great explanation of how these things relate.

It's also worth noting that we also saw some confusion from people not realizing these two use cases for the same feature, and so added some syntax to differentiate the two.


"An existential type is basically a type meaning "any type that implements the given type-class/trait/concept", so you can use to create an array of different types that all satisfy the same contract."

I can't speak for Rust, but when I was first learning Haskell I was bitten by believing it worked that way, but it doesn't:

    Prelude> :t sum
    sum :: (Num a, Foldable t) => t a -> a
    Prelude> sum [1::Int, 2::Int]
    3
    Prelude> sum [1::Float, 2::Float]
    3.0
    Prelude> sum [1::Float, 2::Int]

    <interactive>:25:16: error:
        • Couldn't match expected type ‘Float’ with actual type ‘Int’
        • In the expression: 2 :: Int
          In the first argument of ‘sum’, namely ‘[1 :: Float, 2 :: Int]’
          In the expression: sum [1 :: Float, 2 :: Int]
You can kinda make it work by wrapping it behind yet another type that basically boxes the different underlying types, but that causes its own problems (including, for instance, the fact that no amount of boxing will make this example work; sum is also demonstrating why Haskell won't do this). Basically in Haskell, either do it a different way (and there's usually a different way that will make it work better), or go to full on heterogeneous lists that don't even need the values to be the same type like HList, but unless I've missed an implication of one of the more recent type extensions, you can't have a simple container like this that has heterogeneous types based on their interface. (I know you can make complex ones, because I have myself, but not the way I'm fairly sure you meant.)


In Rust, you do need some kind of indirection here; it is often described as a Box, but it can be any kind of pointer type.


> whereas the contract version would be "a slice of a type that implements io.Reader" but it would have to all be the same type.

From what I understand, you could still pass a []io.Reader to a contract version of the function. A slice of interfaces would still implement the contract (and is the same type, i.e. io.Reader). However, you could also pass a []os.File, which is not possible to do with the interface version of the function.


Sorry, yes, in Go, they could all be "an interface value that is io.Reader" as well. But that is still a subtly different flavor than what the current []io.Reader means.




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

Search: