4 ms·
Contracts support multiple "receivers". If I want to write a function that converts between two arbitrary types, I can write a `func convert(Type1 Type2 Convert
by logicchains 7y ago
Contracts support multiple "receivers". If I want to write a function that converts between two arbitrary types, I can write a `func convert(Type1 Type2 Convertible)(a Type1, b Type1){...}`, where Convertible in a contract with one method that takes a Type1 and receives a Type2. There's no way to do the same thing with interfaces as an interface can only reference a single type, a single receiver.
- jerf 7y agoAnd 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.
- logicchains 7y agoRust 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.
- steveklabnik 7y ago... 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/247298/how-are-rust-traits-different-from-go-interfaces 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.
- jerf 7y ago"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.)
- steveklabnik 7y agoIn 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.
- JelteF 7y ago> 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.
- jerf 7y agoSorry, 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.
- comex 7y agoIf interfaces could have type parameters, then you could just do something like: func convert(Type2, Type1 ConvertibleTo(Type2))(a Type1, b Type1){…} However, that wouldn’t work as well if you wanted multiple methods with different receiver types, e.g. if you wanted to require convertibility in both directions.