(b Box[InType]) Map[OutType any](transformFunction func(InType) OutType) Box[OutType]
Same in Python: def map[U](self, f: Callable[[T], U]) -> Box[U]
vs def map[OutType](self, transform_function: Callable[[InType], OutType]) -> Box[OutType]
and Java: public <OutType> Box<OutType> map(Function<InType, OutType> transformFunction)
vs. public <U> Box<U> map(Function<T, U> f)I understand the desire to keep things concrete and avoid high level abstractions, but it's a decision not to automate stuff that can easily be automated. It runs counter to the basic instincts and purpose of our field/industry. That's why it never sticks.
Most of the time generics might be useful, I’ve ended up needing reflection too anyway. And at that point, I’m really no better off for generics.
The thing is that one use case is so essential, so foundational, that we really can’t just skip it. You need generic containers, for ergonomics and performance. I mean, compare C qsort to C++ std::sort.
The languages that “get around” generics, like PHP, include god containers in the runtime. The language I’m designing is also that way, I’d like to avoid generics preferably forever.
But there’s a tradeoff there. God containers are very flexible, and we’ve seen the ramifications of untyped PHP arrays.
The problem is generics only solve a very small part of the equation: compile time checks for composite types. But to use composite types in anything non-trivial in Go, you then need reflection. Which is slow. And if you then need reflection, you’re already passing interface types anyway plus you’re back to having to handle type-handling errors in the runtime.
So if you’re writing a library that’s expected to have any kind of performance, you’re back to code duplication and having a DoSomethingType() function signatures again.
Or you stick with reflection and take that performance hit PLUS the risk of compile time constraints being runtime errors; which is the a lose-lose scenario. And let’s also not forget that reflection can be just as verbose as code duplication, and harder to get right too.
Don’t get me wrong, I’m glad we have generics. But people on HN massively overstate the value of them in a AOT non-dynamic, strictly typed language like Go.
I guess you could argue that Go has other shortcomings that directly result in generics having limited value. But then you’re basically just arguing that you prefer coding in a different language paradigm, and at that point, you’re much better off using that other paradigm instead of complaining that Go isn’t JavaScript or Haskell.
The Go language itself is never its strength but it has a good runtime, wonderful standard library and tooling. People never picked Go for being an amazing language, but rather for these other things.
In Haskell as well, you can let the compiler infer a lot of things but that doesn’t appear to be the case with this example.
I’d want the compiler to infer things, but that - I think - is at odds with Go desiring a fast compiler, which I also understand.
I think that code would be a lot easier to read if the types were called IN and OUT or In and Out or TIn and TOut or something like that.
I guess the single letter thing is laziness for a part. It's not simple to find words that represent the abstract idea behind the generic type without narrowing the possibilities. For array function, the Key Value from the sibling comment work but for more complex use case, it get complicated.
I think that’s best as you’ll soon learn the “single-character capital letter ⇒ generic parameter” convention
I dont find it confusing, as its pretty clear that it only an placeholder.
In generics the name usually does not matter or is REALLY hard to name so that it makes sense.
More specifically in Go where you have interfaces, concrete types and generics.
val map : ('a Box) -> ('a -> 'b) -> 'b Boxfor (int i=0; i<10; i++) { printf(”%d\n”, i); }
(Or the very similar Go equivalent)
If you having a hard time parsing that, due to the short variable name, i.e. if it’s a huge cognitive load for you, I suggest you switch career, b/c the IT industry is obviously not a good fit.
With that said, Go is explicit with suggesting short variable names for small scopes, and long variable names for bigger scopes. This a good practice in all languages.
There's IList<T> but Task<TResult>
There's Action<T1, T2, T3, T4, T5, T6> but also Dictionary<TKey, TValue> and Map<TIn, TOut>
This stuff kind of "makes sense" once you're used to it, because it's difficult to say what IList<T> ought to have been called otherwise, IList<TContainee> is a mouthful, and Action<T1,...> simply suffers from the inability to specify an unknown number of generic parameters.
https://learn.microsoft.com/en-us/dotnet/api/system.collecti...
https://learn.microsoft.com/en-us/dotnet/api/system.action-2...
https://learn.microsoft.com/en-us/dotnet/api/system.collecti...
Your comment boils down to "I'm smart", which in the end, isn't terribly smart.
Having simplicity and expressiveness as a goal, and a general direction of achieving things through lazy means is at the heart of mathematics and engineering.
Celebrate laziness and a want for simplicity. True simplicity is hard, but worth going after even where it threatens the notion that you're the smartest person in the room.
interface Box<T> { value: T }
function map<T, U>(input: Box<T>, func: (value: T) => U): Box<U> {
return { value: func(input.value) }
}Unless you're writing assembler in vim you're not STEM.
SortBy[T, K comparable](slice: []T, key: func (T) K) func (b: Box[T]).Map[U: any](f: func(T) -> U) -> Box[U]If you don't want it don't use it. It's that simple.
Also, screw those Romans ;)
But anyway I find this in Go much more bearable.
Unless you are a compiler/stdlib vendor or contributing to Boost, there are features that you just don't use it daily.
There are 37000 programming languages, stop forcing every single one that gets popular to look like this.
(b IntBox) MapToStringBox(f func(int) string) StringBox
(b IntBox) MapToBoolBox(f func(int) bool) BoolBox
(b StringBox) MapToIntBox(f func(string) int) IntBox
Etc etc etc?The T, U, and f names are the cognitive load here, because they are meaningless variables. For a specific solution, those would have meaningful names that would make it easier to understand.
Map method
of b (of type Box[T])
that takes f
(of type function that takes value of type T and returns value of type U (which could be any type))
and returns value of type Box[U]
is defined as follows
return Box[U]{v: f(b.v)}
func[U any] b:Box[T].Map(f:func(T)->U)->Box[U]:
return {v: f(b.v)}
func[U any] Box[T].Map(f:func(T)->U)->Box[U]:
return {v: f(this.v)}
// maybe all of the types could be inferred from usage?
func Box[].Map(f):
return Box[]{v: f(this.v)}
Eh... I think you'd need to avoid generics altogether.