mattn / anko

Scriptable interpreter written in golang
http://play-anko.appspot.com/
MIT License
1.45k stars 120 forks source link

Method overriding in custom structs #330

Closed dgrr closed 4 years ago

dgrr commented 4 years ago

Hello,

I am developing a project in need of function overriding. I already developed some on my fork, but I was wondering if you would be interested in merging my commits to the master branch. Let me show you some examples so you can have a better idea of what I am trying to do:

Having:

type C interface {
  Set(interface{})
  Get(k string) interface{}
}

type A struct {
  v int64  
}

func (a *A) Get(k string) interface{} {
...
}

func (a *A) Set(v interface{}) {
 ...
}

I want to call A from the script in a transparent way by using:

# asume `obj` is `A` type
v = obj["key"]
v = 20

That should call:

v = obj.Get("key")
v.Set(20)

That's already implemented in my fork, and you can try it out with your custom structures. I'll wait for your comments and suggestions.

Thanks.

MichaelS11 commented 4 years ago

Thanks for sharing this with us. Great that you are working with Anko.

Currently Anko can call functions that on in structs but struct type definitions are not really there. My idea is to add/improve struct type definitions to Anko. Once that is done, Anko should be able work like Go for making structs and using there functions.

How is that different then what you are working on?

dgrr commented 4 years ago

Hello,

The difference is that my implementation performs those calls transparently. I think I didn't explain my example well. In my fork when you call v = obj["key"] the vm calls obj.Get transparently. It's like doing the following in C++:

void* A::operator[](std::string key) {
...
}

In C++ the compiler will call the operator overload function every time there's a call like obj["key"]. (assume obj's type is A). I implemented that in anko, and I would like to know if you are interested in something like that in the main repo. If you aren't I will keep it in my fork and if you like the idea you can add a section on the README pointing out my repo with the features available.

MichaelS11 commented 4 years ago

Thanks for the link to the repo. I personally think that overloading is beyond the scope of Anko, mainly because there is no overloading in Go.

MichaelS11 commented 4 years ago

Side note, often I see overloading more of a syntax shortcut then really needed. Was curious what is your use case where it is required where another option could not be used?

dgrr commented 4 years ago

I am using anko to transform CSV rows. I already have an efficient parser and I wanted to add the transformation feature. I want to be easy to use by non-programmer users. So doing something like

v, ok = obj["key"]
if ok {
  v = 20
}

requires obj to be a map. Anyway, the v value is not modified if the script doesn't contain obj["key"] = v. To accomplish that I have to:

  1. Change the parser to use a map. (Reduces performance).
  2. Give the user control over the function calls (in this case Get and Set).

I want to keep a balance between performance and easiness of use, and of course, I don't want to modify the parser. So what I did is implementing in the Row struct functions like Get which returns the struct Column which implements Set. So, when I do v, ok = obj["key"], being obj a Row and the returned object a Column, I can easily use the script above without modifying my code, and also, avoiding the user getting in complex language stuff. I know Golang doesn't have any overriding, but an interpreted language can. And yes, it is a syntax shortcut.

MichaelS11 commented 4 years ago

Not sure if I follow exactly but have you looked at using https://godoc.org/github.com/mattn/anko/env#Env.SetExternalLookup

dgrr commented 4 years ago

I think it isn't the same, because my functions doesn't have only the Get. Try this example:

package main

import (
    "fmt"

    "github.com/dgrr/anko/env"
    "github.com/dgrr/anko/vm"
)

type Row struct {
    h []string
    c []Column
}

func (r *Row) Get(k string) Column {
    for i, h := range r.h {
        if h == k {
            return r.c[i]
        }
    }
    return nil
}

type Column interface {
    Set(v interface{})
}

type ColumnInt struct {
    v int64
}

func (c *ColumnInt) String() string {
    return fmt.Sprintf("%d", c.v)
}

func (c *ColumnInt) Set(v interface{}) {
    c.v = v.(int64)
}

type ColumnString struct {
    v string
}

func (c *ColumnString) Set(v interface{}) {
    c.v = v.(string)
}

func (c *ColumnString) String() string {
    return c.v
}

func main() {
    r := Row{
        h: []string{"a", "b"},
        c: []Column{
            &ColumnInt{1},
            &ColumnString{"first"},
        },
    }

    e := env.NewEnv()
    e.Define("row", &r)
    e.Define("println", fmt.Println)

    vm.Execute(e, nil, script)

    fmt.Println("\nAfter exec")
    for _, c := range r.c {
        switch col := c.(type) {
        case *ColumnInt:
            fmt.Println("a:", col.v)
        case *ColumnString:
            fmt.Println("b:", col.v)
        }
    }
}

var script = `
v, ok = row["a"]
if ok {
  println("a:", v)
  v = 20
}

v, ok = row["b"]
if ok {
  println("b:", v)
  v = "hello"
}`
MichaelS11 commented 4 years ago

Right, I am saying if you remove the syntax shortcuts (overloading), the external lookup would work fine.

If it is really important that you have the syntax shortcuts instead of the method calls, then yes, overloading would need to be implement.

Personally I think having method calls can make it easier for people.

dgrr commented 4 years ago

Yes. Said that, would you accept a PR applying those shortcuts?

MichaelS11 commented 4 years ago

Like I mentioned, I personally believe overloading is beyond the scope of Anko. Matt has the final say of accepting PRs.

dgrr commented 4 years ago

In that case, I will keep that in my fork. Thanks.