golang challenge: write a functionwalk(x interface{}, fn func(string))
which takes a structx
and callsfn
for all strings fields found inside. difficulty level: recursively.
Reflection in computing is the ability of a program to examine its own structure, particularly through types; it's a form of metaprogramming. It's also a great source of confusion.
interface
?string
, int
and our own types like BankAccount
.interface{}
which you can think of as just any type.walk(x interface{}, fn func(string))
will accept any value for x
.interface
for everything and have really flexible functions?interface
you lose type safety. What if you meant to pass Foo.bar
of type string
into a function but instead did Foo.baz
which is an int
? The compiler won't be able to inform you of your mistake. You also have no idea what you're allowed to pass to a function. Knowing that a function takes a UserService
for instance is very useful.interface
, confusingly) so that users can use your function with multiple types if they implement whatever methods you need for your function to work.x
). Then we can spy on the function (fn
) passed in to see if it is called.got
) which stores which strings were passed into fn
by walk
. Often in previous chapters, we have made dedicated types for this to spy on function/method invocations but in this case, we can just pass in an anonymous function for fn
that closes over got
.struct
with a Name
field of type string to go for the simplest "happy" path.walk
with x
and the spy and for now just check the length of got
, we'll be more specific with our assertions once we've got something very basic working.walk
fn
is being called with.fn
is correctx
and try and look at its properties.ValueOf
which returns us a Value
of a given variable. This has ways for us to inspect a value, including its fields which we use on the next line.String()
which returns the underlying value as a string but we know it would be wrong if the field was something other than a string.fn
was called with.cases
.val
has a method NumField
which returns the number of fields in the value. This lets us iterate over the fields and call fn
which passes our test.walk
is that it assumes every field is a string
. Let's write a test for this scenario.string
.struct
? In other words, what happens if we have a struct
with some nested fields?struct
by looking at the initialisation.Kind
and if it happens to be a struct
we just call walk
again on that inner struct
.switch
will improve readability and make your code easier to extend.NumField
on a pointer Value
, we need to extract the underlying value before we can do that by using Elem()
.reflect.Value
from a given interface{}
into a function.reflect.Value
of x
so I can inspect it, I don't care how.NumField
on our reflect.Value
but it doesn't have one as it's not a struct.walk
on eitherreturn
to stop the rest of the code executing) and if it's not we just assume it's a struct.walk
on each one. Otherwise, if it's a reflect.String
we can call fn
.walk
but conceptually they're the same.value
is a reflect.String
then we just call fn
like normal.switch
will extract out two things depending on the typeValue
(Field
or Index
)numberOfValues
calling walk
with the result of the getField
function.map
.map
is very similar to struct
, it's just the keys are unknown at compile time.walkValue
which DRYs up the calls to walk
inside our switch
so that they only have to extract out the reflect.Value
s from val
.fn
are done in a particular order.assertContains
is definedchan
.func
.reflect
package.