BooleanCat / go-functional

go-functional is a library of iterators to augment the standard library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Type inference simplifies examples greatly

flowchartsman opened this issue · comments

What happened?

Type inference rules have gotten much better since 1.18, so now you can:

func main() {
	// All even natural numbers (2, 4, 6, 8...)
	isEven := func(n int) bool { return n%2 == 0 }
	evens := iter.Filter(iter.Drop(iter.Count(), 1), isEven)
	for i := 0; i < 5; i++ {
		fmt.Println(evens.Next().Unwrap())
	}

	file, _ := os.Open(`main.go`)
	// All non-empty lines from a file
	lines := iter.Exclude(iter.LinesString(file), filters.IsZero)
	for line := lines.Next(); line.IsSome(); line = lines.Next() {
		fmt.Println(line.Unwrap())
	}

	// String representations of numbers
	numbers := iter.Map(iter.Count(), strconv.Itoa)
	for i := 0; i < 5; i++ {
		fmt.Println(numbers.Next().Unwrap())
	}
}

Which outputs

2
4
6
8
10
Ok(package main)
Ok(import ()
Ok(     "fmt")
Ok(     "os")
Ok(     "strconv")
Ok(     "github.com/BooleanCat/go-functional/iter")
Ok(     "github.com/BooleanCat/go-functional/iter/filters")
Ok())
Ok(func main() {)
Ok(     // All even natural numbers (2, 4, 6, 8...))
Ok(     isEven := func(n int) bool { return n%2 == 0 })
Ok(     evens := iter.Filter(iter.Drop(iter.Count(), 1), isEven))
Ok(     for i := 0; i < 5; i++ {)
Ok(             fmt.Println(evens.Next().Unwrap()))
Ok(     })
Ok(     file, _ := os.Open(`main.go`))
Ok(     // All non-empty lines from a file)
Ok(     lines := iter.Exclude(iter.LinesString(file), filters.IsZero))
Ok(     for line := lines.Next(); line.IsSome(); line = lines.Next() {)
Ok(             fmt.Println(line.Unwrap()))
Ok(     })
Ok(     // String representations of numbers)
Ok(     numbers := iter.Map(iter.Count(), strconv.Itoa))
Ok(     for i := 0; i < 5; i++ {)
Ok(             fmt.Println(numbers.Next().Unwrap()))
Ok(     })
Ok(})
0
1
2
3
4

Though it's worth noting that lines seems to require extra indirection.

Thanks for the issue.

So I thought about this a few days back and was trying to decide wether it made sense to wait until 1.22 (when 1.20 is end-of-life) before updating the examples. What do you think about the trade off of cleaner docs vs. copy-pasting the code not working on earlier versions of Go that are still supported?

I think generics only really shine when type inference is maximally catered to. Without it, generic code tends to be pretty tedious to read, and examples lose that wow-factor that's important to getting someone to give your code a try. I also think that there's no good reason to stick with older versions of generics, since most users making heavy use of them will be clamoring for easier syntax and could use a little push to upgrade.

I guess if it were me, I would change the examples to the more elegant-looking form along with a note that you need the most recent version of Go for the examples to reach their full potential, along with a collapsed pre 1.21 section showing how users of older versions might still use the package.

That sounds reasonable to me. Would you like to PR the change yourself or are you happy for me to do it?

Side note: when 1.22 is released I think it makes sense to bump the version in go.mod to 1.21 and then loads of tests can be simplified as well as the docs. Here's an example:

zipped := iter.Collect[iter.Tuple[int, int]](
    iter.Take[iter.Tuple[int, int]](iter.Zip[int, int](evens, odds), 3),
)

vs.

zipped := iter.Collect(iter.Take(iter.Zip(evens, odds), 3))

That sounds reasonable to me. Would you like to PR the change yourself or are you happy for me to do it?

Nah, go for it! I was just swinging through and thought I'd drop a helpful line

@flowchartsman Are you happy to be listed as a contributor on the next release?