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

Procs Bound Exclusively to Objects #426

Closed
Acmion opened this issue Oct 5, 2021 · 5 comments
Closed

Procs Bound Exclusively to Objects #426

Acmion opened this issue Oct 5, 2021 · 5 comments

Comments

@Acmion
Copy link

Acmion commented Oct 5, 2021

Procs Bound Exclusively to Objects

Abstract

Some procs should optionally be bound exclusively to objects. In practice this would mean that certain procs would only be accessible via object dot notation, rather than accessible directly from the current module.

This could make type imports more convenient and more like how other popular languages do it. This would also clean up the list of current definitions within the current module.

Motivation

  • Having less definitions makes reasoning about code easier.
  • Having less definitions makes certain bugs easier to detect (especially since overloading is possible).
  • Importing types in other languages usually imports their fields and methods.
  • Importing types already imports their fields. These fields can not be accessed without an instance of a type, but currently procs can be accessed without an instance.
  • When using the from-import syntax, one may quickly have to import a bunch of procs just to make a type behave correctly.
  • Some procs have quite general names and can thus be confused with other procs within the current module.
  • All those who use code editors could benefit from this (due to less suggestions).
  • All those who have experience with other programming languages and are learning Nim could benefit from this.

Description

There should be an option to bind procs directly to a specific type and not just leave them as "unbinded" procs. Currently procs operating on objects will always populate the definitions of the current module, even if this is not always necessary nor wanted. As such, intellisense will start suggesting a lot of stuff, even if some of it may be completely impractical (for example, objects that have complicated "constructors"). Not importing these procs is not an option, because otherwise dot notation will not work at all.

Object fields can already not be accessed without the instance and dot notation.

These procs could also be callable via the type itself and would thus act akin to static methods in C#.

Alternatively, this could be added as an option to the import command, where from module import Book.* (note the .*) would import all related procs to Book, but only via dot notation. This would remove the need for cases such as: from module import Book, Book_proc_0, Book_proc_1, ..., Book_proc_n

Implementation alternatives:

  • Allow procs to be bound to a type in accordance with the indentation level (same indentation level as fields).
  • Fix it with a specific import command augmentation, for example, from module import Book.*.

Downsides:

  • Indented procs may require quite a lot of work.
  • Quite an OOP influenced approach and may not as such be completely in line with Nim's approach.

Examples

Before

books.nim:

type Book* = ref object
    year_of_publication*: int
    author*: string
    name*: string

proc was_released_before_or_during_1970*(this: Book): bool= 
    return this.year_of_publication >= 1970

proc calculate_years_since_1970*(this: Book): int = 
    if this.was_released_before_or_during_1970():
        return this.year_of_publication - 1970

    return -1

example.nim:

import books

# works
var book = Book(year_of_publication: 1982, author: "John Doe", name: "Awesome Book")


# works
echo book.name

# works
echo book.calculate_years_since_1970

# works
echo book.calculate_years_since_1970()


# does not work, because it should not
echo name(book)

# works, but it is not as clear that this method actually belongs to book and also a slight contradiction to why name(book) does not work
echo calculate_years_since_1970(book)

example_alt.nim:

from books import Book

# works
var book = Book(year_of_publication: 1982, author: "John Doe", name: "Awesome Book")


# works
echo book.name

# does not work, even if this usually works in other programming languages
echo book.calculate_years_since_1970

# does not work, even if this usually works in other programming languages
echo book.calculate_years_since_1970()


# does not work, because it should not
echo name(book)

# does not work
echo calculate_years_since_1970(book)

After

Note: changes marked with #####

books.nim:

type Book* = ref object
    year_of_publication*: int
    author*: string
    name*: string

    ##### notice the indentation for the procs
    proc was_released_before_or_during_1970*(this: Book): bool= 
        return this.year_of_publication >= 1970

    ##### notice the indentation for the procs
    proc calculate_years_since_1970*(this: Book): int = 
        if this.was_released_before_or_during_1970():
            return this.year_of_publication - 1970

        return -1

example.nim:

import books

# works
var book = Book(year_of_publication: 1982, author: "John Doe", name: "Awesome Book")


# works
echo book.name

# works
echo book.calculate_years_since_1970

# works
echo book.calculate_years_since_1970()


# does not work, because it should not
echo name(book)

##### does not work, because it should not
echo calculate_years_since_1970(book)


##### works, somewhat like static methods in C#
echo Book.calculate_years_since_1970(book)

example_alt.nim:

##### note the .*, which is an alternative for an augmented import
from books import Book.*

# works
var book = Book(year_of_publication: 1982, author: "John Doe", name: "Awesome Book")


# works
echo book.name

##### works
echo book.calculate_years_since_1970

##### works
echo book.calculate_years_since_1970()


# does not work, because it should not
echo name(book)

# does not work
echo calculate_years_since_1970(book)

Backward incompatibility

This would depend on the implementation.

Other

A discussion regarding the problems with code completion when using unbinded functions in Julia.

Even if this would not be implemented in its entirety, I believe that the "augmented import command" could be worth considering.

@n0bra1n3r
Copy link

This seems related to @Araq's more general RFC on type-bound procs which I'm looking forward to: #380.

@Acmion
Copy link
Author

Acmion commented Oct 5, 2021

@n0bra1n3r Yeah, good catch!

As the issue states, operators are even more problematic than standard procs.

@demotomohiro
Copy link

Just copying what other major programming language do to Nim doesn't seems good idea unless there is a good reason.

All those who have experience with other programming languages and are learning Nim could benefit from this.

For people who is not familiar with these feature, that just makes learning Nim harder.
Also adding new features makes language spec complicated and maintaining Nim compiler harder.

Currently procs operating on objects will always populate the definitions of the current module, even if this is not always necessary nor wanted. As such, intellisense will start suggesting a lot of stuff, even if some of it may be completely impractical (for example, objects that have complicated "constructors"). Not importing these procs is not an option, because otherwise dot notation will not work at all.

If a module has so many procs and you want to import a few of them because importing them all cause problem, I think you should split the module to smaller multiple modules.
Even if you can bind procs to a specific type, from module import Book or from module import Book.* can import many unused procs if the imported module defines many procs that are bound to Book object type and you use only a few of them, isn't it?

Having less definitions makes reasoning about code easier.
Having less definitions makes certain bugs easier to detect (especially since overloading is possible)

Can your RFC reduce a number of procs need to be implemented?
Isn't it just make easier to reduce a number of imported procs?

Some procs have quite general names and can thus be confused with other procs within the current module.

tables module defines multiple types (Table, OrderedTable, CountTable) and it defines multiple general name procs (add, len, del, etc).
There are also add, len and del procs for seq type.
Can these same name procs be confusing?
I can use these procs without problems:

import tables

var t = {'a': 5, 'b': 9, 'c': 13}.toTable
t.del 'a'
echo t.len

var s = @[0, 1]
s.del 0
echo s.len

var ct: CountTable[char]
ct.inc 'a'
ct.inc 'b'
ct.del 'a'
echo ct.len

Even if there are same name and same parameter type procs in different module, you can disambiguate a call by adding module name like modulename.procname().
(But I don't think having mutiple procs with same name and same parameter types in different modules are good idea.)

After you wrote following code, when you write a., your intellisense should suggests only procs that has Table type as first paramter.

import tables

var
  a = {1: "one", 2: "two"}.toTable

Anyway, If books.nim defines proc was_released_before_or_during_1970*(this: Book): bool = and it is bound to Book ref object type and example.nim also defines same name proc and need to import Book type, your RFC still doesn't seems to solve the problems.

@Araq
Copy link
Member

Araq commented Oct 14, 2022

More or less a duplicate of #380

@TheGoldMonkey
Copy link

should this be reopened? #380 this was closed but the solution only considers the operations when the name is overloaded.

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

No branches or pull requests

5 participants