d5 / tengo

A fast script language for Go
https://tengolang.com
MIT License
3.54k stars 304 forks source link

reflection wrappers #102

Closed progrium closed 4 years ago

progrium commented 5 years ago

Using Tengo as an expression/REPl language, great work! I noticed you avoid reflection in the objects package, which limits the types you can interop with. I'm hoping to interop with any Go value, so I'm using reflection to wrap structs and their methods. I figure you might not want to have this builtin for performance reasons, but what would be the best way to share this kind of functionality?

d5 commented 5 years ago

Yup. I personally love using reflection. I've tried a lot of different approaches in other projects (like ORM stuffs), but, I avoided it in Tengo for two reasons: performance concerns (which is obvious) and handling/documenting too many edge cases. In my experience, automatic conversion typically involves opinionated (conventions) or guided (e.g. field tag such as json:"") approaches. I'm not saying that it's bad, but, it's just more efforts to make (and keep) them consistent and well documented. To answer your last question: I think having some reflection-based interoperability utilities (mappers or functions) at script package level (instead of putting them in compiler or runtime packages) is okay because, that way, users can consciously choose to (or not to) use reflection based code, based on their requirements. Maybe even better is to keep them in separate package (for example, interop package that contains all different interoperability utilities).

SuperGod commented 5 years ago

I think it is too too complex,if every struct must implement the Object interface. I known reflection will affect performance,and I see qlang can easily call all golang package and method,maybe you can get some inspiration from it. https://github.com/qiniu/qlang

d5 commented 5 years ago

Thanks for the suggestion. I will take a look at qlang.

progrium commented 5 years ago

Yea I’m curious how they let you call arbitrary packages if that’s how it works.

On Wed, Apr 3, 2019 at 12:14 PM Daniel notifications@github.com wrote:

Thanks for the suggestion. I will take a look at qlang.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/d5/tengo/issues/102#issuecomment-479579282, or mute the thread https://github.com/notifications/unsubscribe-auth/AAACh_gzp2ZbquiKGZxqQ-Sb0CvXXMN4ks5vdOF-gaJpZM4a_LXl .

-- Jeff Lindsay http://progrium.com

neclepsio commented 3 months ago

I wrote a simple function wrapper using reflection.

Yes, it's slow, but speed is not always important. Yes, it only supports a small subset (ints, floats, strings), but it could be enough. It is what I need for my use case.

It allows to write something like:

script := tengo.NewScript([]byte(`
    res1 := sum4(1, 2, 3, 4)
    res2 := sprintf("%d - %0.3f", 10, 0.1)
    res3 := round_sum_f32_f64(0.6, 1.6)
    res4 := noret("ciao!")
`))
funcs := map[string]any{
    "sum4": func(a, b, c, d int) int {
        return a + b + c + d
    },
    "sprintf": func(s string, i int, f float64) string {
        return fmt.Sprintf(s, i, f)
    },
    "round_sum_f32_f64": func(f32 float32, f64 float64) int32 {
        return int32(math.Round(float64(f32) + f64))
    },
    "noret": func(s string) {
        fmt.Println(s)
    },
}
for k, v := range funcs {
    script.Add(k, tengoFunctionWrapper(v))
}

compiled, _ := script.Run()
vv := compiled.GetAll()
sort.Slice(vv, func(i, j int) bool { return vv[i].Name() < vv[j].Name() })
for _, v := range vv {
    if v.ValueType() == "user-function:" {
        continue
    }
    fmt.Println(v.Name(), v.String())
}

If you like, I can polish it and open a pull request.