Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow overloading on type name #14

Closed
flaviut opened this issue Dec 1, 2014 · 27 comments
Closed

Allow overloading on type name #14

flaviut opened this issue Dec 1, 2014 · 27 comments

Comments

@flaviut
Copy link

flaviut commented Dec 1, 2014

It's impossible to overload on a type name, ex.

type
  Foo = object
    a: int

proc Foo(): Foo =  # fails to compile
  return Foo(a : 213)
proc Foo(val: int): Foo =  # also fails to compile
  return Foo(a : val)

Ideally, this would be possible and possibly even unified with the type construction syntax.

@Araq
Copy link
Member

Araq commented Dec 1, 2014

IMHO we shouldn't do this, but I'm leaving it open so people can discuss.

@refi64
Copy link

refi64 commented Dec 1, 2014

If this were to be added, I'd prefer it to be like destructors:

proc create(): Foo =
    return Foo(a: 213)
...

For consistency reasons.

@PavelVozenilek
Copy link

~~One name has several different meanings. What could go possibly wrong?~~~

Edit: changed opinion. Added a comment down bellow.

@flaviut
Copy link
Author

flaviut commented Dec 2, 2014

@kirbyfan64 Nim has a long tradition of using overloading, so it would be consistent. I wouldn't mind the create() sort of syntax though.

@PavelVozenilek Of course the programmer should exercise discretion when using it, but Nim already provides a thousand different ways to shoot yourself in the foot, what's one more? 😉

@superfunc
Copy link

@kirbyfan64 isn't this just a regular function as we already have?

@refi64
Copy link

refi64 commented Dec 2, 2014

@superfunc No; It'd work like destructors do. With destructors, Nim automatically invokes the destroy function with the object as its argument. With the syntax I showed, it's the same basic idea; Nim invokes the create function implicitly; you never see it happen.

Basically, create is to destroy as constructors are to destructors.

@superfunc
Copy link

That makes sense, my bad

@PavelVozenilek
Copy link

I think there's use case for this.

Say I define a type and want to limited functionality on it, via templates

# --- a_lib.nim ---
type
  MyFile* = object
     name : string
     handle : int

template foo*(ff : expr, s : string, body : stmt) {.immediate.} =
  block:
    var ff : MyFile
    ff.name = s
    ff.handle = 0
    echo ("name = ", ff.name)
    body

# --- main.nim
import a_lib

foo(f, "name"): 
  echo "Do something with 'f' of being type MyFile"

Here I need to use helper name foo. If I would be allowed to use ``MyFile` it would mean one superficial name down:

# --- main.nim
import a_lib

MyFile(f, "name"): 
  echo "Do something with 'f' of being type MyFile"

@flaviut
Copy link
Author

flaviut commented Jan 27, 2015

Related: http://forum.nim-lang.org/t/703

@Henry
Copy link

Henry commented Aug 16, 2015

I would like to see this feature added as it provides consistency between the construction of built-in types and user-defined types. Also it improves readability of the code when objects are constructed as arguments for function calls.

@Araq
Copy link
Member

Araq commented Aug 17, 2015

I would like to see this feature added as it provides consistency between the construction of built-in types and user-defined types.

There is no inconsistency right now, the compiler treats every type the same.

@Henry
Copy link

Henry commented Aug 17, 2015

Sure, but if I need to create a special constructor for a user-defined type it has to be named differently whereas constructors for built-in types are named the same as the type. If you were to add another constructor for a built-in type would you also name it the same as the type or differently?

@Araq
Copy link
Member

Araq commented Aug 17, 2015

I think you're misunderstanding what int(x) means: It is a type conversion, not a type construction, it always is, there is no special logic in the compiler that treats int differently from object Foo.

If you were to add another constructor for a built-in type would you also name it the same as the type or differently?

One cannot name it the same.

@Henry
Copy link

Henry commented Aug 17, 2015

I am referring to constructors of the form:

var student = Student(name: "Anton", age: 5, id: 3)

I would like to be able to create other constructors for object types and it would be convenient and, at least to me, consistent to also name them the same.

One cannot name it the same.

Right, currently not, but if you could would you? If not what convention would you use and why should the other constructors be named differently given that overloading is used for other functions?

If you/others are interested in having support for this overloading do you have some idea of how much effort it would take to implement it?

@Varriount
Copy link

@Henry Those aren't constructors, at least, not in the usual sense. It's merely syntactic sugar for simple object initialization. I don't see how the syntax differs between user types and built-in types.

@Henry
Copy link

Henry commented Aug 17, 2015

OK, sure, they do not create a location or allocate storage, they are basically initializers. So how do a create a set of object initializers which have the same name as the object type or provide the syntactic sugar so that they appear so? Basically how do I write types so that they look and behave like built-in types but with additional initializers? This is the consistency I was referring to.

@Varriount
Copy link

I still don't see what you mean by "like built-in types". Built-in types like integers are assigned to directly, while sequences and strings are created using regular procedures, like newSeq and newString. Perhaps you could include a comparative example?

@Henry
Copy link

Henry commented Aug 17, 2015

Take the example

var student = Student(name: "Anton", age: 5, id: 3)

This is a user-defined object type initialized using the default/builtin initializer. Now imagine Student could be created/initialized in other ways than simply providing all the elements as arguments:

var student2 = Student("Anton", database)

OK this is a trivial example but in the applications I would like to translate from C++ to Nim there are lots of complex classes which can be constructed/initialized is many ways and in practice it is convenient if these functions name the same as the class (standard C++ constructor naming) particularly when constructing a temporary instance as an argument to a function e.g.:

var success = process(Student("Anton", database))

@dom96
Copy link
Contributor

dom96 commented Aug 17, 2015

The convention for constructors is to define a new proc named initType if it creates a non-ref type and newType if it creates a ref type.

@Henry
Copy link

Henry commented Aug 17, 2015

I understand, in which case shouldn't we use:

var student = initStudent(name: "Anton", age: 5, id: 3)

Why is the name of the type used for built-in initializers and initType for user-defined initializers? Wouldn't it be better to overload the type name? Do you prefer

var success = process(initStudent("Anton", database))

or

var success = process(Student("Anton", database))

@Bulat-Ziganshin
Copy link

Bulat-Ziganshin commented Jul 15, 2018

Can we consider type definition as automatically defining magic function(s) with the same name constructing object from field values? :

type Foo = tuple a,b: int

# defines the magic function
proc Foo(a,b: int): Foo

type Bar = object case kind: bool of
    true: n: int
    false: b: bool

# defines the magic functions
proc Bar( kind:static[true], n: int): Bar
proc Bar( kind:static[false], b: bool): Bar

and user can provide other overloads of Foo/Bar and even overwrite these default definitions with his own ones (or just discard them).

Also, my arguments for using Foo/Bar as object constructors: https://github.com/nim-lang/Nim/issues/7474#issuecomment-405100389

@dom96
Copy link
Contributor

dom96 commented Jul 15, 2018

That's already the case...

Bar(kind: true, n: 42)

https://nim-lang.org/docs/manual.html#types-object-construction

@Bulat-Ziganshin
Copy link

Bulat-Ziganshin commented Jul 15, 2018

Well, my point - can we just add these auto-generated definitions to the procedures list and then allow user to provide other procedures with the same name but different parameters? What is the catch - is it hard to implement or it doesn't look useful?

And yeah, currently object constructor require field names. For Algebraic datatypes similar to Haskell it will be great to allow using these constructors without field names.

Similarly, it will be great to allow to use field names in tuple constructor. I.e. in both cases generate a procedure with the same name as type and parameter names equal to the field names, and allow to overload this procedure.

@narimiran narimiran transferred this issue from nim-lang/Nim Jan 2, 2019
@metagn
Copy link
Contributor

metagn commented Feb 11, 2021

The comments here don't really indicate a consensus, but I'm guessing it's likely this will not be done? Do we need to revitalize discussion or is this safe to be rejected?

@Araq
Copy link
Member

Araq commented Feb 11, 2021

There are ideas about adding constructors via a type bound =init operation (and it might be a good feature) but I don't know if that is related to "overloading on type name".

@metagn
Copy link
Contributor

metagn commented Feb 11, 2021

I think the other RFCs about initialization cover enough about initialization, this RFC seems to specifically be about being able to giving routines type names. Another similar proposal might be like being able to define a call operator on typenames, not specifically related to custom initializers but related to the semantics of how custom initializers could be done.

@timotheecour
Copy link
Member

timotheecour commented Feb 11, 2021

I don't like this RFC (overloading on type), it creates ambiguities:

type Foo = object
  x: int
proc Foo(x = 1): Foo = Foo(x: x)
echo Foo().x # 0 or 1?
echo Foo is proc # true or false?
Foo.bar # can MCS happen ? (ie: Foo().bar)

the following already works today, works in generic code, is explicit and has 0 ambiguities:

type Foo = object
  x: int
type Bar1 = object
  y: int
type Bar2[T] = object
  y: T
type Baz = object
  x: int
type Goo[T] = object
  x: T
proc init(T: typedesc[Foo], x = 123): T = T(x: x)
proc init(T: typedesc[Bar1 | Bar2], y = 123): T = T(y: y)
proc init[U](T: typedesc[seq[U]], n = 0, cap = 0): T =
  if cap == 0: result = newSeq[U](n)
  else: (result = newSeqOfCap[U](cap); if n > 0: result.setLen(n))
proc init[U](T: typedesc[Goo], x: U): Goo[U] = Goo[U](x: x)

echo Foo.init()
echo Foo.init(x=2)
echo Foo.init(2)
echo Bar1.init(2)
echo Bar2[int].init(2)
echo seq[int].init(2)
echo seq[int].init()
echo seq[float].init(cap = 3)
echo Goo.init("abc") # infers Goo[string]
doAssert not compiles(Baz.init(2))

IMO these are better than newFoo/initFoo pattern because it works in generic code.

the case of default constructors (ie, var a: Foo and Foo.default) is the topic of #290 (=default type bound constructor)

IMO we should close this RFC

related

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests