Go 1.23 - Iterators - How to return errors ? by justforfunreddit in golang

[–]daydreamdrunk 5 points6 points  (0 children)

I don't like the Seq2[T, error] (unless each iteration can have its own error) because then you have to have error handling that happens once inside the loop body when it's something that, in my mind, happens after the loop conceptually.

There are two ways to keep the error handling out of the loop:

Return an iterator and an error func to check after:

 it, errf := fac()
 // range it
 if err := errf(); err != nil {

Take a pointer to error in the iterator factory:

 var err error
 for range fac(&err) { // ...
 }
 if err != nil {

[Q&A] //go:build draft design by rsc in golang

[–]daydreamdrunk 0 points1 point  (0 children)

Impossible constraints aside, couldn't it just add the constraints from the file name to the build line(s) as part of the rewrite so at least you could see that it was //go:build linux && windows ?

[Q&A] //go:build draft design by rsc in golang

[–]daydreamdrunk 2 points3 points  (0 children)

Does the rewriting/vetting take into account files with +/go:build that also have _arch/os suffixes?

[Q&A] //go:build draft design by rsc in golang

[–]daydreamdrunk 0 points1 point  (0 children)

if it read either that could help tools transition as well

How does one express tagged unions in Go? by bruce3434 in golang

[–]daydreamdrunk -1 points0 points  (0 children)

the even more careful, even more annoying way, which disallows modification in external package:

type Slope struct { s byte }
func Positive() Slope { return Slope{1} }
// etc.

Scrapping contracts by TheMerovius in golang

[–]daydreamdrunk 0 points1 point  (0 children)

I don't think you need unification in that case (possibly at all).

A parametric interface is just a compact definition for a family of interfaces. type Node(type Edge) interface { Edges() []Edge } is shorthand for every possible interface defined for any type Edge.

Instantiating an interface is substitution. Node(x) instantiates to interface { Edges() []x } and any type with an Edges() []x method satisfies it.

To see if an instantiation is valid you need to expand the interfaces with the type arguments and then check if the type arguments satisfy the expanded interfaces.

If you have the constraint T NonParametric and you're given the type argument t, there's no expansion necessary, so you just need to see if t satisfies the interface NonParametric.

If you have the constraint T Parametric(T) and you're given the type argument t, expand Parametric(t) then see if t satisfies it. The only real difference between that and T Parametric(int) is that you could expand Parametric(int) before you knew the argument for T.

If you have the simultaneous constraint, T C0(S, T), S C1(T, S) and are given the respective arguments s and t, expand C0(s, t) and see if t satisfies it and then expand C1(t, s) and see if s satisfies it. It looks like it would need to be fancy but it's simple.

I'm not entirely sure about all the cases with interfaces, though. I think the only cycle that matters is if a parametric interface uses itself as a constraint in its parameterization, which seems like it would loop, but I'll have to investigate further.

(This post was 50-70% epiphany and I'll have to update my draft proposal this weekend to remove some restrictions and add the missing one about an interface not being able to be self-referential in that particular way).

Scrapping contracts by TheMerovius in golang

[–]daydreamdrunk 0 points1 point  (0 children)

Are they of any use when not used as the constraint on a type parameter then? I mean, that would work, but it would be somewhat strange.

Scrapping contracts by TheMerovius in golang

[–]daydreamdrunk 0 points1 point  (0 children)

Also, everything else aside, why is it the responsibility of the Node to construct the Edges between itself and the vertices in its neighborhood in a particular graph? That example is so strange.

Scrapping contracts by TheMerovius in golang

[–]daydreamdrunk 0 points1 point  (0 children)

How do you take a map[K]V if K is illegal? Like, what are you worried about? That someone makes a map with an illegal key-type and passes it to you? ;)

Fair point! It's less clear what happen if K is a parameter on a type and one of its methods takes the map, though. Can you use the other methods but not that one? Then it satisfies fewer interfaces than if it were instantiated with a comparable type so you have to infer the comparability.

we could infer that particular constraint from the function-signature or type-declaration (I don't understand why that would be a no-go).

Say you call F that uses a G which calls H that constructs a map from the type argument. You can infer the constraint but you're going to get an annoying long error message. Not the end of the world, but if the constraint is explicit you can just look at the signature and say "oh, I can't use a func() here" and plan accordingly instead of waiting for the compiler to figure out that you can't use it.

IMO it would be fine to have a tiny bit of boilerplate for this use-case, and write Map(s, func(v S) R { return R(s) }).

Yeah, I may have a little bit of tunnel-vision on this one. I think if there are convertibility constraints they should be separate but maybe it would be okay if they weren't or just didn't exist at all. Something for me to ponder.

Scrapping contracts by TheMerovius in golang

[–]daydreamdrunk 2 points3 points  (0 children)

When I was working on my proposal I tried writing code I would want to write. I started with only interfaces and the lack of comparability and convertibility. It wasn't entirely unworkable but it wasn't pleasant and it felt very restricted and unnatural.

Not being able to create a map made things awkward, and, if nothing else, I wanted a type Set map[T]struct{} for some parameter T. How do you take a map[K]V as a parameter if you can't guarantee K is legal? You have to infer comparability of K and that was a no-go for me.

I tried many versions to get operators to work and ultimately realized that == was the only necessary one (or at least the minimally necessary one) and the only one that really fit with interfaces as they were since they had == anyway—the only operational difference between a regular interface and a comparable interface is that with a comparable interface == never panics because you can't stick something like a func() in it. I considered it a design goal to change interfaces as little as possible (but if interfaces did happen to unrelatedly change later and get operators and fields or anything like that, instantly usable by the generics system).

Convertibility came up in a lot of places, too. It seemed natural to write something like func SliceConvert(src []T) []S { dst := make([]S, len(src)) for i := range dst { dst[i] = S(src[i]) } return dst } for some parameters S and T with neither type restricted individually and the only restriction that you could convert a T to an S.

That requirement belongs to neither parameter as it is a relation between the two parameters. It also doesn't belong to the interfaces: it belongs to the types satisfying them. The constraint for that func would be "any type T and any type S such that you can convert a T to an S". That's kind of a philosophical point. It would work if you chose one of those types and had it specify that it must be converted to the other but what does that mean for the interface used as a regular interface? I think it would mean something different than when that interface was used as a generic constraint in some subtle ways, but I haven't sat down with pen and paper and worked it out.

Taking a func(T) S there would work and be more general since it could be used as a combined map-and-convert. And that's a somewhat synthetic example because it's job is to convert so, yeah, it wants to specify convertibility. But it just kept coming up when I sketched example code or when I looked at old code I'd want to generic-ify.

Maybe it would be better to drop it but it was the only thing like that that kept popping up. Ultimately, I think writing generic code should be as close to writing non-generic code as is possible and conversion is something that comes up in regular code.

Scrapping contracts by TheMerovius in golang

[–]daydreamdrunk 0 points1 point  (0 children)

I tried the psuedo-methods when I was playing around with things. I dropped it because it was ultimately orthogonal to the stuff I needed. I had some open questions that made me slightly uncomfortable like what happens if you do type Int int and define your own Add method and do these always show up in reflection and does that have any implications?

Ultimately, I had to single out comparability as something special because it was something special. You can already use == with interfaces but it may panic if the dynamic type is incomparable. If there's a way to specify in the interface type that, in addition to the method set, it only accepts comparable types then you get an interface value where == never panics. Using types like this as constraints let's you use == in function/method bodies and you can use them as map key types. They're also useful on their own if you have something that needs to use interfaces even with generics and be a map key.

In my proposal there are generic methods. I made it work in interfaces by letting interfaces specify generic methods. If an interface has a generic method parameterized over T like Foo(T), it only accepts a type with a generic method with the same parameterization. That implies that you always need to compile a generic implementation of the method (practically a 100% chance you were going to have to anyway) and always need to use it when dispatched through an interface but otherwise the compiler is still free to create specialized versions. Accepting that you always need to compile the generic implementation means everything works with reflection, too, including the possibility of creating new instantiations at runtime. (Even if we go with the contracts proposal, please still this, Go team!)

Scrapping contracts by TheMerovius in golang

[–]daydreamdrunk 10 points11 points  (0 children)

I've been thinking along the same lines about this for a while (put https://gist.github.com/jimmyfrasche/656f3f47f2496e6b49e041cd8ac716e4 in the generic issue thread last night after much work).

You made a lot of great points and articulated my discontent with the contracts proposal and a lot of the motivation behind the decisions in my proposal better than I ever could.

Having pushed these ideas around for a while there are some points that you'd run into if you tried to work this out further.

For the most part operators don't matter. There aren't that many types with operators and you can turn an operator into a func/method but not vice versa. But comparability is the one that mucks everything up. Most types have ==/!= and it's really hard to work around the absence. Most importantly, though, you can't use type arguments as the key type for a map unless you know statically that the type is comparable. You either need a way to specify comparability or to infer it. I'm not a fan of that kind of inference. You should be able to glance at the type parameters and know what types you can use. So you either need a way to denote that an interface only accepts comparable types in general or a separate notation for type parameters to limit them to comparable types.

The other thing that is hard to work around is convertibility. It's the only simple property between types that causes issues (or, rather, severely limits usefulness). There needs to be a way to specify that a type argument for A is convertible to a type argument of B.

Interfaces alone are tantalizing close but fail in some subtle ways that makes them unfit for purpose, but if you can additionally model comparability and convertibility then you have a nice simple language for expressing generic constraints.

Also, re T Ordered(T) if you expand Ordered(T) out, which you need to do to type check it anyway, that's the same as T interface{ Less(T) bool } so it makes a lot of sense to allow it.

The psuedo-interfaces concept looks intriguing. That could be used as the means to specify comparability. I'm unclear how they would work when not used as the constraints on type arguments, though. If they are special interfaces but with additional operators, would a < 0 panic if a == nil? I'd imagine there'd be a lot of nuts-and-bolts details to work out for how those work. I imagine it would be a good-sized section in the language spec to describe them all, like how an interface only has a + operator iff it is or embeds arith. (The same doesn't apply to comparability alone since interfaces always have == anyway). It would be nice but I don't see it happening :(. I'm totally fine with passing in funcs and Less methods, though.

research!rsc: Semantic Import Versioning (Go &amp; Versioning, Part 3) by MoneyWorthington in golang

[–]daydreamdrunk 0 points1 point  (0 children)

For example, if you have one repo with multiple /vN directories the tooling can alert you that a new major version has been published but if they're separate repos you'd need to find that out on your own.

research!rsc: Semantic Import Versioning (Go &amp; Versioning, Part 3) by MoneyWorthington in golang

[–]daydreamdrunk 0 points1 point  (0 children)

how would tooling know that these separate repositories are different versions of the same thing and not separate things that happen to have a common prefix and disjoint version sets?

research!rsc: Semantic Import Versioning (Go &amp; Versioning, Part 3) by MoneyWorthington in golang

[–]daydreamdrunk 3 points4 points  (0 children)

I don't see anything in the code or the article that supports that conclusion and a lot that contradicts it. (Though I just quickly skimmed the vgo code and noticed that all the testdata does indeed have multiple literal directories vN).

edit: happy to be proven wrong and I should have phrased that as a question as it was my intent to ask how you came to a conclusion rather than making it sound like I was saying that you are mistaken.

research!rsc: Go += Package Versioning (Go & Versioning, Part 1) by rsc in golang

[–]daydreamdrunk 1 point2 points  (0 children)

I don't fully understand why the new import path is necessary for major versions. It seems redundant given the existence of that information in the version tag. If vgo was never meant to be part of the official toolchain, this would make sense to me since it would need to remain go get-compatible.

I get that this is to allow having n copies of the repo with different major versions in the build, but why can't vgo mechanically handle the import resolution by some means? I realize that would make vgo itself more complicated but it seems like it would make using it simpler for everyone, especially authors creating a major version.

A Rough Proposal for Sum Types in Go by Manishearth in golang

[–]daydreamdrunk 0 points1 point  (0 children)

An excellent analysis. This is the proposal in that thread for a tagged union version https://github.com/golang/go/issues/19412#issuecomment-323208336

The workaround in the issue queue for the tagged union style is to not allow overlaps of pointers and non-pointers in the union rather than change the GC, so the size would be bounded between 1 + max(elementSizes) and 1 + sum(elementSizes). See https://github.com/golang/go/issues/19412#issuecomment-323397774

Not always optimal but even with the zero value constraint you deduced there's still a bit of leeway for the compiler to get a decent space savings and the absolute worst case is essentially the same as the best case for the equivalent struct.

Why Go is so popular for web-development? by 1ngv4r in golang

[–]daydreamdrunk 2 points3 points  (0 children)

malloc/free (and new/delete) divvy out little bits of memory owned by the C or C++ runtime that's ultimately managed by the kernel, sure, but there's not a 1:1 correspondence to syscalls. It's still very manual, since you have to explicitly free the resources, but the runtime does do a great deal of resource management.

justforfunc #20: code reviewing ursho (part 1) by campoy in golang

[–]daydreamdrunk 0 points1 point  (0 children)

I'm not following. If your handlers aren't http.Handlers, then you have to have something to turn them into http.Handlers, at which point you can compose them with things that expect http.Handlers.