objectionary / eo

EOLANG, an Experimental Pure Object-Oriented Programming Language Based on 𝜑-calculus
https://www.eolang.org
MIT License
1.01k stars 126 forks source link

do we need generics? #1

Closed yegor256 closed 3 years ago

yegor256 commented 7 years ago

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:

List<String> list = new ArrayList<>();
for (String item : list) {
}

Can't we do this:

List list = new ArrayList();
for (String item : list) { // we just type cast here automatically
}
vivekimsit commented 7 years ago

What if we store some complex object like Tree or Map?

yegor256 commented 7 years ago

@vivekimsit the same. Every time we need an object, we say what is the type of it, and it runtime type casting is happening.

mdbs99 commented 7 years ago

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.

scadgek commented 7 years ago

What about type safety - no need for it, too?

mdbs99 commented 7 years ago

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.

goqp commented 7 years ago

But Yegor's example doesn't compile. You mean should we be able to? I think so.

SilasReinagel commented 7 years ago

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.

mdbs99 commented 7 years ago

@goqp I'm not talking about Yegor's example. but of the new syntax.

mdbs99 commented 7 years ago

@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.

SilasReinagel commented 7 years ago

@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.

mdbs99 commented 7 years ago

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.

hemangandhi commented 7 years ago

@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.

mdbs99 commented 7 years ago

@hemangandhi I'm not sure about Duck-typing either. Is just a thought.

OneWingedShark commented 7 years ago

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.)

annenkov commented 7 years ago

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) {
}
hussein-aitlahcen commented 7 years ago

@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.

OneWingedShark commented 7 years ago

@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.)

annenkov commented 7 years ago

@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" :)

darkf commented 7 years ago

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.

nawfalhasan commented 7 years ago

@darkf, nailed it.

apocarteres commented 7 years ago

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.

OneWingedShark commented 7 years ago

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.

nqafield commented 7 years ago

Herb Sutter on C++ templates... ;) https://youtu.be/xnqTKD8uD64?t=55s

apocarteres commented 7 years ago

@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.

ElwinBran commented 6 years ago

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.

Miha-x64 commented 4 years ago

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]

dan1els commented 3 years ago

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.

yegor256 commented 3 years ago

We don't need generics. Period :)

0crat commented 3 years ago

Job gh:cqfn/eo#1 is not assigned, can't get performer