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

How to convert multiple impl class to base protocol type? #136

Closed
griffith1deady opened this issue Jun 25, 2024 · 18 comments
Closed

How to convert multiple impl class to base protocol type? #136

griffith1deady opened this issue Jun 25, 2024 · 18 comments

Comments

@griffith1deady
Copy link

Hi! I'm trying to figure how i can make that code work:

import oolib

protocol Session:
    proc connect()
    proc disconnect()

protocol Readable:
    proc read()

class PlayerSession impl (Session, Readable):
    proc connect() = discard
    proc disconnect() = discard
    proc read() = discard

proc resolveBase(session: Session) =
    echo session

let playerSession = PlayerSession.new()
resolveBase(playerSession.toProtocol())

Error message:

Expression: resolveBase(toProtocol(playerSession))
  [1] toProtocol(playerSession): tuple[connect: proc (){.closure.}, disconnect: proc (){.closure.}, read: proc (){.closure.}]

Expected one of (first mismatch at [position]):
[1] proc resolveBase(session: Session)

As i see, by default there way todo this doesn't exists. My strange idea in generating conversion procedure for each procotol type for class, like playerSession.toSession(), playerSession.toReadable(). Or exists some better way?

@griffith1deady
Copy link
Author

I'm know, that i can make that working by that:

let playerSession = PlayerSession.new()
let playerProtocol = playerSession.toProtocol()
resolveBase((connect: playerProtocol.connect, disconnect: playerProtocol.disconnect))

but isn't that ugly way?

@glassesneo
Copy link
Owner

The features of protocol follow the ones of interface in other languages, so I'm not planning to make a feature that interface doesn't have. I guess a procedure that requires Session type like resolveBase() should be defined in Session. But I have an idea to make something extending the features of class and protocol for users.

@glassesneo glassesneo closed this as not planned Won't fix, can't repro, duplicate, stale Jun 25, 2024
@glassesneo
Copy link
Owner

Sorry, my misunderstanding.
Some cases where you want to make a seq of Animal like below won't work without any work around.

protocol Animal:
  proc eat

protocol CanFly:
  proc fly

class Cat impl Animal:
  proc eat =
    discard

class Bird impl (Animal, CanFly):
  proc eat =
    discard
  proc fly =
    discard

let
  cat = Cat.new()
  bird = Bird.new()

var animals: seq[Animal] = @[]
animals.add cat.toProtocol() # type: tuple[eat: proc()]

# not compiled!
animals.add bird.toProtocol() # type: tuple[eat: proc(), fly: proc()]

I'll fix this problem somehow.

@glassesneo glassesneo reopened this Jun 30, 2024
@griffith1deady
Copy link
Author

Yeah, that's what i originally meant. Seem's like converting is hard, because as i think we can lose original inheritance of class. For example, when we even has way to convert Bird to Animal, is we lose original type information, so we can't then

for animal in animals:
  echo animal.isInstanceOf(Bird) ## false even when original class is `Bird`

?

@glassesneo
Copy link
Owner

The return value of toProtocol() is just a tuple whose signatures are the same as its original protocol(, which enables you to regard the different classes as one tuple type), so you can't tell whether it's an instance of a certain type. Since there is a limit on checking type information, we have to give it up at some point. The main issue is that you can't unify the different classes implementing the same protocol, although their signatures are also the same. I have several ideas on it, so please wait til their completion.

@glassesneo
Copy link
Owner

@griffith1deady
Add pick macro in 8509b54.
Please check tests dir for usage. I'll implement other lacking features later.

@griffith1deady
Copy link
Author

griffith1deady commented Jul 4, 2024

So, sadly we never can get feature like that in JVM?

val sender = (event.handler as NetHandlerPlayServer).playerEntity

NetHandlerPlayServer is class that implements INetHandlerPlayServer interface
and event.handler is INetHandlerPlayServer

@glassesneo
Copy link
Owner

Does that mean the conversion from protocol to class, not from class to protocol? I want to make it too, but I still don't come up with the way to implement it.

@glassesneo
Copy link
Owner

Defining a converter for converting each to the other may help you.

@griffith1deady
Copy link
Author

Does that mean the conversion from protocol to class, not from class to protocol? I want to make it too, but I still don't come up with the way to implement it.

Yeah, but not only from protocol to class, but two way conversion. Like class to protocol with converters (for auto picking correct protocol automatically, like does in other languages) and itself from protocol to class.

@griffith1deady
Copy link
Author

griffith1deady commented Jul 5, 2024

About realization: I'm not sure in what way you make conversion from class to protocol, but if you make that like closure procedures, then we can use some unsafe code. Accessing Environment from closure directly and protect original class by GC_ref that stored in env to ensure we dont can usage-after-free

@glassesneo
Copy link
Owner

How I made the conversion from class to protocol is probably simpler than you think it is.
In class macro, we can get the signatures of both class and protocol and define toProtocol() like new constructor.

@glassesneo
Copy link
Owner

glassesneo commented Jul 5, 2024

I made #139 for this issue with a draft.

@griffith1deady
Copy link
Author

How I made the conversion from class to protocol is probably simpler than you think it is. In class macro, we can get the signatures of both class and protocol and define toProtocol() like new constructor.

how about making protocol's internally storing class reference?
like:

protocol Session:
    proc connect()
    proc disconnect()

would be expanded into something like:

type Session[T: ref object] = tuple[self: T, connect: proc(), disconnect: proc()]

and then we can introduce something like ueCast in unreal engine, that would be like internally:

proc classCast[T: tuple, C: ref object](protocol: T, class: typedesc[C]): C = protocol.self 

and we can make that working for non-ref classes easily by something like:
proc toProtocol() =
result.self = new(typeof(result.self))

@griffith1deady
Copy link
Author

@glassesneo just friendly ping

@glassesneo
Copy link
Owner

Thank you for your suggestion, but something suddenly came up and I have to concentrate on my another project for approximately 2 months.
Your idea looks so good that I will implement class-protocol conversion based on it after finishing that.

@griffith1deady
Copy link
Author

griffith1deady commented Jul 19, 2024

@glassesneo
don't worry, it's okay, i understand
by the way, i had nothing to do just now, so i decided to play with this whole system, and i have to tell you that you can leave it the way it is now.

import oolib

proc getEnvironment[T: proc](
    procedure: T
): pointer = {.emit: "`result` = `procedure`.ClE_0;".}

proc getProcedure[T: proc](
    procedure: T
): pointer {.inline.} = {.emit: "`result` = `procedure`.ClP_0;".}

protocol ReferenceInjection:
    proc resolve(): string

class Injection impl (ReferenceInjection):
    var count: int
    proc resolve(): string =
        return "Injected"

type InjectionEnvironment = object
    sup: RootObj
    colonstate: int
    self: Injection

let referenceObject = Injection.new(count = 5)
let reference = referenceObject.toProtocol()
echo reference.resolve is proc(): string {.closure.} # true
echo reference.resolve is proc(): string {.nimcall.} # false

let referenceEnvironment = getEnvironment(reference.resolve)
let referenceProcedure = getProcedure(reference.resolve)
var environmentReference: Injection
copyMem(addr environmentReference, cast[pointer](cast[int](referenceEnvironment) + 16), sizeof(pointer))

echo referenceEnvironment.repr() # 00007F040D13F060
echo referenceProcedure.repr() # 000063E7C3BBE1F0

echo resolve.repr() # 000063E7C3BBE1E0

echo referenceObject.repr() # Injection(count: 5)

echo environmentReference.repr # Injection(count: 5)

var env: InjectionEnvironment
copyMem(addr env, referenceEnvironment, sizeof(InjectionEnvironment))
echo env.repr # InjectionEnvironment(sup: RootObj(), colonstate: 0, self: Injection(count: 5))

doAssert(referenceObject.count == 5 and env.self.count == 5, "Injection count is not equal")
# Assertion never failed.
echo env.self of Injection # true

it's a very unsafe code, but i can live with it.

@glassesneo
Copy link
Owner

Thank you for your understanding. I'm glad there is something that uses my work! I'll be back in two months.

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

2 participants