For the best experience on desktop, install the Chrome extension to track your reading on news.ycombinator.com
Hacker Newsnew | past | comments | ask | show | jobs | submit | history | more ianlancetaylor's commentsregister

One step at a time. It's certainly a natural extension, and I don't think anything in the current design draft precludes it.


Unfortunately [T] is also ambiguous with the current Go syntax. The parser can't distinguish an array declaration from a generic type.


How about the following:

  contract Addable(t T) {
  	t + t
  }

  func Sum.<T Addable>(x []T) T {
	var total T
	for _, v := range x {
		total += v
	}
	return total
  }
It has the nice property that it's somewhat analogous to type conversions, the grammar should be unambiguous, and it's much less visually confusing than re-using parentheses IMO.


Thanks for the comments. We tried for some time to declare contracts like interfaces, but there are a lot of operations in Go that can not be expressed in interfaces, such as operators, use in the range statement, etc. It didn't seem that we could omit those, so we tried to design interface-like approaches for how to describe them. The complexity steadily increased, so we bailed out to what we have now. We don't know whether this is is OK. We'll need more experience working with it.

I'm not sure that Go is a language in which higher-level abstraction is appropriate. After all, one of the goals of the language is simplicity, even if it means in some cases having to write more code. There are people right here in this discussion arguing that contracts adds too much complexity; higher order polymorphism would face even more push back.


Yeah, I’m not sure higher-level abstraction is right for Go either—like I said, I do think the tradeoffs in the generics design are ultimately reasonable, given the design constraints.

I guess it’s those very constraints that get to me. This is going to sound harsh, but I believe the goal of “simplicity” is already very nearly a lost cause for Go.

The language is large and lacks any underlying core theory, with many features baked in or special-cased in the name of simplicity that would have been obviated by using more general constructs in the first place; the language is not simple, it’s easy for a certain subset of programmers. Adding convenience features here and there and punting on anything that seems complex by making it an intrinsic has led to a situation where it’s increasingly difficult to add new features, leading to more ad-hoc solutions that are Go-specific. This leads to the same problem as languages like C++: by using Go, developers must memorise details that are not transferable to other languages—it’s artificial complexity.

A major source of this complexity is inconsistency. There are few rules, but many exceptions—when the opposite is more desirable. There are tuple assignments but no tuples. There are generic types but no generics (yet). There are protocols for allocation, length, capacity, copying, iteration, and operators for built-in containers, but no way to implement them for user-defined types—the very problem you describe with contracts. Adding a package dependency is dead-easy, but managing third-party dependency versions is ignored. Error handling is repetitive and error-prone, which could be solved with sum types and tools for abstracting over them, but the draft design for error handling just adds yet another syntactic special case (check/handle) without solving the semantic problem. Failing to reserve space in the grammar and semantics for features like generics up-front leads to further proliferation of special cases.

Virtually all of the design decisions are very conservative—there’s relatively little about the language that wasn’t available in programming language research 40 years ago.

I don’t mind writing a bit of extra code when in exchange I get more of something like performance or safety wrt memory, types, race conditions, side effects, and so on. But Go’s lack of expressiveness, on the whole, means it doesn’t provide commensurate value for greater volume of code. But it could! None of these issues is insurmountable, and the quality of the implementation and toolchain is an excellent starting point for a great language. That’s why I care about it and want to see it grow—it has the immense opportunity to help usher in a new era of programming by bringing new(er) ideas to the mainstream.


In this case the mismatch is between a type argument and the way that a type parameter is used, so it's not a type mismatch/error, it's a meta-type mismatch error. You are suggesting that the meta-type be inferred from the function (and any functions that it calls) rather than being explicitly stated. That is doable--C++ does it--but it means that the user has to understand the requirements of an implicitly inferred meta-type. The Go design draft suggests instead that the user be required to understand an explicitly stated contract.


Yes, the various fmt.Print functions only return an error if Write on the underlying Writer returns an error.


I don't think there is any lack of clarity about your List example; that is clearly forbidden. The example that is less clear is the one that generates a million types and then stops.

A similar issue arises in C++; the C++ standard says that there is an implementation defined limit on the total depth of recursive instantiations. Perhaps Go should do something similar.


C++'s method is certainly a practical way to limit expansion on inductively defined data types. I think a better way would be to construct a type system _opposite_ of most constructed today (which focus on the ability to express problems) and instead intentionally limit the types we are able to construct to those with "easy to use" and computationally efficient properties.

Consider this sample. We want to be able to define inductive types such as `List(T)`, but not `InfiniteList(T)` or `BigArray(T)`. While `BigArray(T)` is an interesting construct (it's effectively a dependent type)* it jumps past the "can I keep it in my head" smell test for me. As soon as a I have to reason deeply about what a type constructor does it just doesn't feel like Go to me.

So we want to be able to construct types which are inductive but can only calculate a single type. List{T} calculates one type, BigArray calculates _n_ types, and InfiniteList calculates an infinite number of types.

* In Idris one would write something like:

  data BigArray : (n : Nat) -> (l : List m) -> Type where
      Z l : Vect Z v
      n l : Vect n l


That is not the only difference. Exceptions travel through calling functions if there is no catch clause. The check construct does not.


When every function call uses "check", we end up with the same control flow as exceptions, which is almost always what I want. You only get different behavior where you don't use it.


The default handler just propagates the error up to the caller.


And then the caller has to either check or ignore the error. The error never propagates to the caller's caller without an explicit action in the caller. That is unlike exceptions.


What is an "unmarked check call"?


But it will propagate through a chain of unmarked check calls. If the requirement to mark potentially-throwing call sites is what disqualifies an error handling scheme as belonging to the class of exception systems, then we need a new term that includes both exceptions and this proposal and excludes everything else, because the proposed Go scheme very closely resembles exceptions.


An unmarked check call would mean explicit error handling like if err != nil {} which isn't unchecked..it's just checked not using `check`. If you don't have this, then your error variable will be unused an you'll get a compile error.


A year ago we said it was time to move toward Go 2: https://blog.golang.org/toward-go2 . This is another step in that direction.


Then I'm a year late in asking why the Go team is now deciding to say yes to something they said no to for over half a decade.


The blog post explains that.


Yes, what you write should work with the current draft.


Cool. It isn't something I'd want to abuse, but there are places in my code where it would clean things up nicely, too.


Read the design draft (https://go.googlesource.com/proposal/+/master/design/go2draf...). The issues you raise are addressed.


Not quite. You'd need to write new `handle err { ... }` blocks for every new context, like how they have one in the for loop. In effect you're just moving the handling of the error away from the place it occurred, which is not optimal. As someone who writes Go every day I don't see this strategy effectively cutting down on boilerplate core or adding clarity.


Consider applying for YC's Summer 2026 batch! Applications are open till May 4

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

Search:

HN For You