-2

I am new to go, and wanted to implement a scheduler that will recieve tasks(i.e interval + handler). I got stuck in the part where I should get the handlers as go is a static language(I can't get a generic function signature). I came up with the following solution: each "handler" func will get wrapped by empty-interface:

type GenericFunc interface{}
add_task(GenericFunc(my_func), p1, p2,...)

which will result in the following struct as for saving the handler and its params:

type Task struct {
   handler reflect.Value
   params []reflect.Value
   ...
}

Tasks execution would be invoked by:

func (t *Task) execute() {
    t.handler.Call(t.params)
}

My question is this the right way to treat generic functions or is there a more idiomatic solution I'm missing? thx.

  • Duplicate from https://stackoverflow.com/questions/42726986/generic-functions-in-go – Andre Batista Jan 15 '20 at 15:11
  • all the the links provided do not solve the problem asked here. also, I think its better to remove the duplicated comments as people might actually use this info. – Gal Rotenberg Jan 16 '20 at 06:44
  • If you believe the duplicate links don't answer your question, please explain why. By my reading, they are directly applicable. – Jonathan Hall Jan 16 '20 at 08:13

1 Answers1

3

In its simplest form a better abstraction would be to use a simple func() type for the handlers, and if parameters are required, callers should pass a closure that captures the required arguments. So you don't have to care about them at all, and you don't have to resort to using reflection.

Allowing these functions to report success or failure is something reasonable and desirable, so use a function type func() error.

A possible implementation:

type Task struct {
    handler func() error
}

func (t *Task) execute() {
    if err := t.handler(); err != nil {
        fmt.Println("Failed to execute task:", err)
    }
}

Now let's create example jobs:

func simpleJob() error {
    fmt.Println("simple")
    return nil
}

func complexJob(s string) {
    fmt.Println("complex", s)
}

This is how we can use them in Tasks:

t := &Task{handler: simpleJob}
t.execute()

t = &Task{handler: func() error {
    complexJob("data")
    return nil
}}
t.execute()

Which outputs (try it on the Go Playground):

simple
complex data

We were "lucky" with simpleJob(): its signature matched what we needed for the handlers, so we could use it as-is. Not like complexJob() which has a completely different signature: it has a string param and does not return an error, but with an anonymous function (a closure) we could still use it for Task.handler.

If more control or reporting capabilities are required from the jobs, then a proper interface should be created for them, and values of such interfaces should be submitted for execution, not just simple functions.

For example:

type Job interface{
    Execute() error
    Progress() int
    Result() interface{}
}

type Task struct {
    job Job
}

func (t *Task) execute() {
    if err := t.job.Execute(); err != nil {
        fmt.Println("Failed to execute task:", err)
    }
}
icza
  • 389,944
  • 63
  • 907
  • 827