#Dependency Injection into Generic Function

31 messages · Page 1 of 1 (latest)

zealous socket
#

Hi,
I'm wondering how I can inject dependncies (databases, services etc) into generic functions. Ideally, I would like a struct and an interfface so I can mock it correctly but haven't been able to do it.

type Fulfilment interface {
   *TestStruct
   SetWhatever()
)

func Transform[T Fulfilment](order Order) T {
    var fufilment T
    return fufilment
}

func (o *TestStruct) DoWhatever() {}

type TestStruct struct {}

This works, but I can't find a way to add external dependncies that is not on TestStruct


type TestStruct struct {}

func Inject() func[T Fulfilment](order Order) T {
 // I'm getting Function type cannot have type parameters
}
frozen thicket
#

is there a reason why doing the simple thing is insufficient?

#

the simple thing being just passing parameters to functions

zealous socket
#

I have about 5 external dependencies such as a database, services etc that I need to pass to a type before calling the Transform function. If I call it straight off the back I will have to pass a considerable amount of params and I don't think it's very testable.

frozen thicket
#

you are creating a dichotomy that does not exist.
being explicit about what you need does not make the code less testable

#

I would argue the opposite

#

if your problem is having too many dependencies, then your problem is having too many dependencies, as silly as that sounds
sweeping them under the rug does not make them go away

zealous socket
#

Thanks for your response.
How would you go about doing this? Passing in a struct with them all in? I’m still unsure how you can mock generic functions tho 🤔

frozen thicket
#

it depends on the context
if I'm working with a type in which every method needs D1 and D2 and some methods use a mix of D3, D4, and D5 then I gravitate towards having D1 and D2 as fields and passing the rest as parameters.

I suppose another way to phrase it as would be to identify which are the fundamental dependencies of a given type, they have a high likelyhood of being a good fit for fields.
For instance, if you wrap a, say, http.Client it's rather silly to require the caller to pass one for every method given your fundamental purpose is to wrap it

zealous socket
#

I guess using generics takes away some fundamental testing in Go. Reassigning functions (or declaring them) or not being able to mock them properly.

#

I see what you mean. The error Function/Method cannot have type parameters is tripping me up. If I call a generic function and pass dependencies. I can see no way of mocking that generic function.

frozen thicket
#

I don't understand your problem

#

and I mean that literally

#

not being snarky or anything

#

and when I say your problem, I mean that literally too, not the circumstances in which you find yourself

#

that sentence is a bit dense, what I mean is that if you are building a table and choose o use a drill which has run out of batteries, the problem is building the table, not charging the drill

zealous socket
#

I have a function that calls something:

// Package A...

type Service struct {
   Store redis.Cache
   // I would usually do this (stub for tests), but I cant because Go won't allow it.
   genericFn func[T any](whatever string, store redis.Cache) error 
}

func (s Store) myCaller() {
  // When it comes to testing this, I cannot mock it, I cannot add the genericFuncti
  _ = packageB.GenericFunction[myType]("whatever", s.Store)
}

// Package B...

func GenericFunction[T any](whatever string, store redis.Cache) error {
   return nil
}

This would be ideal, but (I think) it's not possible in Go:

func MyThing struct {}

func (m *MyThing) GenericFunction[T any](order Order) T {
    var fufilment T
    return fufilment
}
frozen thicket
#

why is it a function pointer instead of a method?

zealous socket
#

That’s where I was falling down. I wasn’t sure how to flesh out a struct that has a generic method off it. Whereby I don’t have to pass a type when initialising the struct (hope that makes sense)

frozen thicket
#

why don't you want to pass a type when initializing the struct?

#

(these are the kinds of questions we have to make when we're talking about a solution rather than the problem :p)

#

what is the problem here?

#

what are you trying to solve?

zealous socket
#

Because when I call the generic function, the type is only established within the caller.

#

Let me write some more code one sec

#
// Package A...

type Service struct {}

func (s Service) myCaller() {
  // Here is when I discover the type that I need to use.
  // It's a A (for example)
  _ = packageB.GenericFunction[packageB.A]("whatever")
}

// Package B...

type Something interface {
   DoWhatever()
)

type Service struct {
    Redis cache.Store
    // I cant do this, but this would be great.
    Something
}

func GenericFunction[T Something]() T {
    var something T
    return something
}

type A struct {}
func (a A) DoWhatever() {}

type B struct {}
func (b B) DoWhatever() {}
frozen thicket
#

This still doesn't tell me what problem you are trying to solve

When we write code, we do it for a purpose. We do it because we're trying to solve a problem.
The code doesn't matter, what matters is the problem that you have that prompted you to write this code, what is it?

#

otherwise all I can tell you is "go doesn't have generic methods, good luck!" which... do I even have to spell out how unhelpful that is? sunturtle

zealous socket
#

Yea no worries.

lethal cedar
#

I was trying to develop an Encoder. And I was trying to add a method [T int | int8 | ...]Encode(v T) to encode things, then I can use static check to see if I miss called Encode.