nim-lang / RFCs

A repository for your Nim proposals.
137 stars 23 forks source link

default object generic types #469

Open ghoomfrog opened 2 years ago

ghoomfrog commented 2 years ago

Abstract

generic object type definitions should allow default generic types to be specified, so when instantiating, the generic type could be omitted

Motivation

laziness

Description

code intention could be unclear if you don't know the type's definition

Code Examples

this should work:

type O[T = int] = x: T
var x = O(x: 99)

where the second line expands to:

var x = O[int](x: 99)

Backwards Compatibility

No response

avahe-kellenberger commented 2 years ago

You could just do something like

type Foo[T] = object
  x: T

proc newFoo(x: int): Foo[int] =
  Foo[int](x: x)

var x = newFoo(99)
beef331 commented 2 years ago

This seems to just be a workaround version of "Object constructors should infer the type". There is no reason one needs to specify default specialization if the following is valid.

type O[T] = object
  x: T
var x = O(x: 99)
konsumlamm commented 2 years ago

This seems to just be a workaround version of "Object constructors should infer the type". There is no reason one needs to specify default specialization if the following is valid.

This feature would still be useful to save some typing in the common case where the type parameter is the default type. Imo it's useful for the same reason default parameters for routines are useful: you avoid repeating the default parameter or having to define a new type alias. Another use case I can think of is if the type parameter isn't the type of a field directly, but rather determines some "strategy" to use (this would probably be a static generic in Nim).

Better type inference would certainly be nice regardless though.

beef331 commented 2 years ago

Well I thought this was going to be a few sentences but I have written a bit more.

This feature would still be useful to save some typing in the common case where the type parameter is the default type

Is this really a common case? I've personally never had a generic that had a "default" generic parameter as it defeats the purpose of generics.

My main concern with any changes to what a generic base symbol is that it can ruin generic composite typeclasses which are a godsend when writing generic code.

Let's look at a simple example.

type MyObject[size: static int = 1] = object
  when size == 1:
    a: string
  else:
    a: float
proc doThing(myObj: MyObject) = discard

With an addition of a default generic parameter what would the MyObject symbol mean. Would it still be generic typeclass? Would it be a case of having a default generic means that specialization is used, but if you have none it's a typeclass? Might it be the specialized MyObject[1]? These have large side effects on other code

If it's the former then we just made it so we now need to do MyObject[1] so we did nothing except make object constructors less readable by removing an non inferable specialization from the callsite. As all MyObject()s would be MyObject[1] except in type semantics so var a: MyObject would not compile.

Changing the semantics of MyObject to be dependent on whether MyObject has any default parameters makes code much much harder for the programmer to reason about. Consider the following:

# module a.nim
type 
  MyObject*[T = int] = object
  SomeOtherObject*[T] = object

# module b.nim
import a
proc doThing(myObj: SomeOtherObj) = discard # This covers all
proc doThing(myObj: MyObject) = discard # Only our specialized version comes here, we only know this if we read the `T = int`

# module c.nim
import b
doThing(SomeOtherObject[int]()) # Compiles
doThing(SomeOtherObject[float]()) # Compiles
doThing(MyObject()) # Compiles
doThing(MyObject[string]) # Does not compile
doThing(SomeOtherObject()) # Does not compile
# It's very unclear here why these fail to me.

With the final proposed case I have we've made it so type classes need to be written MyObject[auto] which is more code, and given that it's very common(in my experience) to use composite type classes(consider ref or ptr or seq for instance) we've made the more common use case more complicated (ref[auto] or ptr[auto] or seq[auto]). With more complex generics this means array for instance is now array[auto, auto] making code more redundant.

konsumlamm commented 2 years ago

This feature would still be useful to save some typing in the common case where the type parameter is the default type

Is this really a common case? I've personally never had a generic that had a "default" generic parameter as it defeats the purpose of generics.

I meant that the common case when using a type with a default generic parameter would be to not specify the parameter. In that case, it saves some typing. I didn't mean to imply that default generic parameters themselves are commonly needed. Why would it defeat the purpose of generics though? All it essentially is is sugar for typing out the default parameter.

With an addition of a default generic parameter what would the MyObject symbol mean. Would it still be generic typeclass? Would it be a case of having a default generic means that specialization is used, but if you have none it's a typeclass? Might it be the specialized MyObject[1]? These have large side effects on other code

That is a good point, probably good enough to not accept this RFC. The only workaround I can think of would be requiring MyObject[] for defaulting, but that would be pretty ugly and I don't like it.