Closed yegor256 closed 3 years ago
What if we store some complex object like Tree
or Map
?
@vivekimsit the same. Every time we need an object, we say what is the type of it, and it runtime type casting is happening.
We don't need generics, only adapters. And would be easy create any adapters just copying objects. Today we need to name a new a class for a new adapter, but not with EO lang.
What about type safety - no need for it, too?
Type safety is good, of course. But think why we need them? Generics his was invented to reuse code. But if we have delegation, we don't need to use them.
But Yegor's example doesn't compile. You mean should we be able to? I think so.
It seems that generics would be essential. Most Java 8/.NET programmers that I know don't use for loops anymore. Without generics we couldn't do this:
List<SaleItem> items = ...
items.forEach(x -> x.postToEbay());
The power of generics used with anonymous method seems like something that would be costly to lose.
@goqp I'm not talking about Yegor's example. but of the new syntax.
@SilasReinagel this is functional programming... IMHO the new language should be simple and use only one way to do something. Java — and many others — has many ways to do the same thing. I think this is wrong.
@mdbs99 Then what would you propose be the mechanism that replaces the for
operator? It is not object-oriented.
Furthermore, it is really expedient to need to create a separate class for an item and a collection of items? Must we create Color
and Colors
if we wish to send messages to a group of an item instead of a single one?
My concern is for the practicality of minimize the amount of code and classes that programmers have to create to build and use their objects.
Then what would you propose be the mechanism that replaces the for operator? It is not object-oriented.
@SilasReinagel sorry, I did not express myself well. I did not mean that forEach
is wrong, just that we would have to have a single 'version'. Today we can use a classic for
or forEach
.
Furthermore, it is really expedient to need to create a separate class for an item and a collection of items? Must we create Color and Colors if we wish to send messages to a group of an item instead of a single one?
We won't have classes. So, I think Types (as Interfaces) will be enough. Perhaps we need to take a look in Duck-typing concept.
@mdbs99 Please don't duck-type... It ruins pretty much everything. It makes it hard to know what objects do what and makes the whole idea of types
redundant. If you duck-type in various cases, why not duck-type everywhere?
I think generics are very important as they allow for more abstraction and better collections. Consider a SortedList
:
type SortedList extends ArrayList:
But now, there must be a way to enforce that elements of the sorted list are not only comparable but comparable to one another.
type SortedList extends ArrayList<T extends Comparable<T>>
Another possible workaround would work if types are first class objects:
type type:
List @methods
List @supers
ctor(List methods, List superclasses):
@methods = methods
@supers = supers
Python does this (see here). This might let you have types as first-class objects and further allow "generic types" to become normal arguments to normal constructors and methods. Like:
type SortedList extends ArrayList:
Boolean isOfType(Object arg)
object sortedListOfInt as SortedList:
//Some fields
Boolean isOfType(Object arg):
typeof(Int).implementedBy(arg)
This will move things to runtime and add the weight of reflection in the implementation.
So I think the choices are duck-typing (ew), generics (maybe), and types being types themselves (very complicated, but has been done). I like the idea of types being types also because it reduces the amount of "magic" in the programming language.
@hemangandhi I'm not sure about Duck-typing either. Is just a thought.
A lot of modern generic systems (C#, Java, Delphi) are actually fairly anemic -- the reason is that being able to parametrize on functions, procedures, values, and other generics in addition to types results in two interesting properties, which I'll name later. (I'll use Ada for the examples because it allows these in its generics and I'm familiar w/ it.)
A simple operation like swap with just the parameter types as generics doesn't really show anything, but it's the first step to showing off the first property:
Generic
Type Element is Private; -- Says we know nothing about the type, except that it has assignment.
Procedure Generic_Swap( X, Y : in out Element );
-- Implementation of generic.
Procedure Generic_Swap( X, Y : in out Element ) is
Temp : Constant Element := X;
begin
X:= Y;
Y:= Z;
end Generic_Swap;
There's your bog-simple type-parametrization, but if we have the ability to have generics themselves as parameters we could say something like:
Generic
Type Element is Private;
Type Index is (<>); --- "(<>)" indicates a discrete type in Ada.
-- "Index range <>" indicates that the range is unspecified/indefinite.
Type Data_type is Array(Index range <>) of Element; -- Any array-type of element indexed w/ Index.
-- The "is new" indicates an instance of a generic.
with procedure Swap is new Generic_Swap(Element); -- Specifically Generic_Swap parametrized w/ Element
-- Here we specify the function to use as "<", our comparison function.
with function "<"(Left, Right : Element) return Boolean;
Procedure Generic_Bubble_Sort( Data : in out Data_type );
Procedure Generic_Bubble_Sort( Data : in out Data_type ) is
Finished : Boolean;
Temp : Element;
begin
loop
Finished := True;
for J in Data'First .. Index'Pred (Data'Last) loop
if Data (Index'Succ (J)) < Data (J) then
Finished := False;
Temp := Data (Index'Succ (J));
Data (Index'Succ (J)) := Data (J);
Data (J) := Temp;
end if;
end loop;
exit when Finished;
end loop;
end Generic_Bubble_Sort;
As you can see, we can use a transitive dependence to ensure that Generic_Bubble_Sort's Swap
parameter (which is an instance of Generic_Swap
) if parametrization on generics is allowed.
The second thing, which might have escaped notice, is the specification of the "<" operator as a parameter; this is dependency injection, the first of the two properties I promised. (To reverse the sort we simply pass in ">" to the parameter instead.) And exactly where the speaker in this video leads up to.
This leads to the second interesting property: static polymorphism. We can say something like:
Generic
type P is private;
with Function As_Text( Item : P ) return String;
Padding : Positive := 8; -- default to 8.
Pad_Character : Character := ' '; -- default to space.
Procedure Padded_Print ( Item : P );
-- Implementation
Procedure Padded_Print( Item : P ) is
Pad : constant string(1..padding) := (others => Pad_Character);
begin
Put_Line( Pad & As_Text(Item) );
end Padded_Print;
And Bam! there we have the Decorator pattern, in generics, and completely statically. (And in Ada that'd work with non-tagged [ie non-OOP] types as well.)
I think, that generics more about type safety than about code reuse. You could reuse code just by pretending that all the lists are just list of objects and inserting type casts where needed. In this paper: http://homepages.inf.ed.ac.uk/wadler/gj/Documents/gj-tutorial.pdf authors say the following:
GJ comes with a cast-iron guarantee: no cast inserted by the compiler will ever fail...
GJ was a prototype for Java generics.
For example, this, clearly, should not be allowed:
List list = new ArrayList();
list.add("a");
list.add(1);
for (Int item : list) {
}
@annenkov I totally agree with your point, generic mean type safety, not code reuse. So, could we prohibit code reuse with generics ? I think that's the real question @yegor256.
@annenkov, @hussein-aitlahcen
I disagree; generics should be the method for code-reuse.
Objects aren't intended to reuse code, this explicitly comes up as an example in the video Nothing is Something at about 29 minutes; though you might need to back up to understand the whole example.
Generics, on the other hand, can give you that "pluggable module" solution she presents as a solution -- but even more, if the generic-system is robust enough you can use it as the method for composition as well (that would allow you to define objects in terms of generic constructs -- which, transitively, would mean that since this is to be a purely OO language, optimizations of generics would benefit everything and inheritance could be modeled as a generic-dependency to the parent object.)
Also, it's quite possible to make generics single-implementation, which means that all instances of a generic share the same code; thus if generics are used as the method for inheritance you could get a lot of implicit code reuse. (But remember, inheritance is not for code-reuse.)
@OneWingedShark I think I just didn't formulate my thoughts properly :) What I meant is, that generics were added to Java for type safety reasons. Because, obviously, you can reuse the same code by treating, say, all elements of a collection as instances of the top class in the hierarchy and insert a runtime typecast at usage points. I was looking at the example in the very beginning of this post. This example does allow runtime typecast (which can fail at runtime). If we allow this kind of behaviour, then there is no need in generics. Although, we lose static type safety guarantees, that's why I think it's a bad idea. On the other hand for statically typed code generics, being a form of parametric polymorphism, are definitely a good way of code reuse. So, my new formulation: "generics are for type safe code reuse" :)
From your own README:
These things we don't tolerate:
[...]
- type casting
These things we want to build in:
static analysis
It would be foolish to directly go against your own principles. There is no reason for a modern type system to not provide generics.
@darkf, nailed it.
did anyone propose something similar to C++ templates? I think it is a good way to go, cause type gets derived from template on compile time and it's not possible to mix up types in runtime, so type casting is not required.
The problem:
Let's assume some messy and UNSAFE form with generics:
//declare generic-based storage
type SortedSet<T extends Comparable<T>> {}
//declare user type
type Item extends Comparable<Item> {
bool compare(Item that) {...}
}
//generic-based storage specification
type Inventory extends SortedSet<Item> {}
//use case
SortedSet set = new Inventory();
set.add(new String()) // this is there things blow up
which might be safely transformed to
// declare template for all comparable types. T points always to implementor
template Comparable<T> {
compare (T that)
}
// sorted set which can work only with "comparables" of specified type
template SortedSet<Comparable<T>> {
add (T element);
}
// instantiating templates.
// cause Comparable is template and it has T as param we can infer it's type
// in place of instantiating.
// Like this one:
type Item extends Comparable {
compare (Item that) {
//compare this and that safely
}
}
type Sword extends Item {
hitEnemy();
}
// user type
type Inventory extends SortedSet<Item>{}
// generated SAFE code. It's safe cause you can't use instantiated templates with something distinct from concrete compiled type.
class Item extends Comparable<Item> {} // only self comparing allowed
class Sword extends Item {} // ok sword will be always an item
class Inventory extends SortedSet<Item> {} // only instances of Item can be injected and anyways all the stuff treated as Item.
//use cases:
Item item = new Item();
Item swordItem = new Sword();
Sword sword = new Sword();
SortedSet badSet = new Inventory(); // compile error Inventory is not a SortedSet
SortedSet<Item> badSet = new SortedSet<Item>(); // compile error SortedSet is a template and can't be used directly.
SortedSet<Item> items = new Inventory(); // it's ok, reference is a template of "sorted set of items" and value is template instantiation of the same type.
items.add(new Sword())// ok a Sword is item, we can keep it in this storage cause storage support Item's
Item item = items.next(); // it's ok
Sword sword = items.next(); // compilation error cause Inventory knows nothing about Sword.
So templates can be involved as abstract types which can't be instantiated directly but expose some generic behavior and require specification and do it with respect of type safety.
Also it's worth to limit the template only with single parameter which will allow to let inference system derive most the things correctly with lesser effort and reduce code verbosity.
did anyone propose something similar to C++ templates?
C++ templates are a bad deal -- for one thing, they are Turing-complete, which means that absent meta-rules you can never be sure that their evaluation will ever stop. (C++ has a meta rule to prevent this, but that is "putting a band-aid on it" instead of dealing with the real problem.)
This article details the differences between C++ Templates and Ada Generics, although it's somewhat wrong in a few places. Namely the do_common_thing
example.
Herb Sutter on C++ templates... ;) https://youtu.be/xnqTKD8uD64?t=55s
@OneWingedShark yes, this is why i explicitly mentioned "something similar" there. Let's assume, if we pick up an idea that template has to be compiled before runtime and used in runtime only with that type it was initially compiled (no wildcards so no Turing completness) it doesn't seem as bad generic-purpose as it appeared in C++ language.
My point mentioned above is targeted on fact of dramatically reducing template responsibility and functionality up to required minimum.
I think the Type Safety that generics usually provide, make code clearer. But a system needs to be designed to limit the amount of parameters and parameter depth.
Some examples on generics in different languages: Clojure — nope, dynamic typing TypeScript, Python — type hinting, lint checks Java, Kotlin, Scala, Ceylon, ~J#,~ C#, F# — yes Groovy, Dart — ±, both generics and dynamic typing supported, [IMO]awful languages[/IMO] Go — there are some 'generic' collection types hardcoded into language, similarly to Java arrays C++ — oh, my... Rust — yes, C++-like compile-time code generation, but with Java-like strictness and determinism Swift — yes, compile-time codegen within module, runtime metadata passing for exported functions with stable ABI JS, PHP — no; [IMO]very bad languages[/IMO]
I believe generics are important to have, just to prevent casting errors on the compile time. Your examples just are like old java stuff, and just remember how painful was to have that stuff w/o generics. I probably left that java diamond operator stuff and used var/let/auto syntax for code shortening.
We don't need generics. Period :)
Job gh:cqfn/eo#1
is not assigned, can't get performer
I don't know, that's why asking. Do we really need generics? What is the benefit of them? Can't we just declare the type at the time of its usage?
This is how it works now in Java:
Can't we do this: