I write this kind of code a lot when working in Elm (whose generics are
in a similar spot to the place you suggest). It's not the worst, and
there various approaches in related languages use to make the "adaptors"
more ergonomic.
I still like the idea of using interfaces to express constraints better,
but I think it's worth discussing where this might go. Some
observations:
* We don't actually need an adaptor for the zero value, since we can
just declare a variable of the generic type:
func MyFn(type T) {
// x is the zero value, per usual initialization rules:
var x T
// ...
}
* If we don't have any kind of bounding on type variables, then we can
actually omit type parameter lists entirely, and use a lexical
convention to distinguish between concrete and generic types. OCaml
does this by denoting type parameters with a single quote, so 't is a
type variable while t is a concrete type. Borrowing that convention we
could write e.g:
func MapSlice(slice []'src, f func('src) 'dst) []'dst {
// ...
}
...which reduces verbosity a bit.
Haskell and Elm both use case to distinguish, with uppercase variable
names being concrete and lower case being type variables. I like that
a bit better, but Go already uses case to distinguish exported vs.
private.
* Your suggestion of default adaptors is similar to Scala's implicit
parameters: https://docs.scala-lang.org/tour/implicit-parameters.html
One problem with this design, which bounding solves: Suppose we have a
generic set type implemented using binary trees (I will use
the 't notation discussed above, but you could use explicit parameter
lists as well).
type Order('t) interface{
Less(x, y 't)
}
type Set('t) struct {
// ...
}
func (s Set('t)) Insert('t; Order('t))
func (s Set('t)) Member('t; Order('t)) bool
// etc.
What happens if we accidentally pass different adapters to different
calls to the methods? e.g:
type CaseSensitive struct {}
type CaseInensitive struct {}
func (CaseSensitive) Less(l, r string) bool
func (CaseInsensitive) Less(l, r string) bool
// ...
var set Set(string)
set.Insert("Apple"; CaseInensitive{})
set.Insert("Animal"; CaseInsensitive{})
set.Insert("acorn"; CaseInsensitive{})
// Returns false!
set.Member("acorn"; CaseSensitive{})
Depending on scoping issues and how you infer the adaptors, this could
happen by accident, because of different adaptors being visible in
different scopes.
This is a known problem with scala implicits, whereas with rust
traits/haskell type classes, we're able to guarantee that there is only
one instance of an "adaptor" for a given type. But this means giving up
on automatic interface satisfaction, which is... no.
There's a paper that you'll find if you google "ocaml modular implicits"
that solves this issue for OCaml using OCaml's module system, and while
I have some ideas about how we *could* extend Go with the necessary
functionality, it's a much bigger change to the language than adding
bounds based on interfaces.
Quoting Patrick Smith (2018-10-16 06:33:39)
> Yet another generics discussion
> at� [1]https://gist.github.com/pat42smith/ccf021193971f6de6fdb229d68215
> 302
> This one looks at what programmers would be able to do if very basic
> generics were added to Go, without contracts. Generic functions may not
> use methods or operators of their type parameters.
> The answer is quite a lot, actually. The code would be verbose, but not
> impossibly so.
>
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to [2][email protected].
> For more options, visit [3]https://groups.google.com/d/optout.
>
> Verweise
>
> 1. https://gist.github.com/pat42smith/ccf021193971f6de6fdb229d68215302
> 2. mailto:[email protected]
> 3. https://groups.google.com/d/optout
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.