2

Consider a processing system which ingests objects from an external source and performs extensive processing. One example could be objects detected by a computer vision system which are then fed into a security alert system that looks for particular movement behaviors, and abnormal positioning.

The external object provides its position in its preferred reference frame. My code needs to transform that position into one that is consistent with the processing system.

One OO approach would be to build a "heavy" wrapper class which internally transforms the position. Heavy since it contains or calls code for the transform instead of blindly passing the external object fields through. Benefit here is self-contained caching is possible.

The FP approach would be to build a static function which accepts the object and computes the transformed position. If this function is called many times it might make sense to cache the computed position, and in the FP world that cached position would belong in a separate lookup structure.

Here's some Python pseudocode:

OO approach:

import visionlib as v

class WrappedObject:
    def __init__(self, external_object):
        self.position = Utils.transform_frame_a_to_b(external_object.get_position())
    def get_position(self):
        return self.position

def get_objects_in_frame():
    return [WrappedObject(obj) for obj in v.get_sensed_objects()]

def update_tracks(object_list):  # from ()

    # Lots of loops, specialized processing, optimization, statistical calculations, etc.
    # Calls WrappedObject.get_position() thousands of times per frame
    ...
        ...
        pos = obj.get_position()
        ...
    return object_tracks

# Call
tracks = update_tracks(get_objects_in_frame())

Functional approach:

import visionlib as v

class WrappedObject:
    def __init__(self, external_object):
        self.obj = external_object
    def get_position_in_frame_a(self):
        return self.obj.get_position()

def get_objects_in_frame():
    return [WrappedObject(obj) for obj in v.get_sensed_objects()]

def get_object_positions(object_list):
    return dict([obj.id, Utils.transform_frame_a_to_b(obj.get_position_in_frame_a())
                for obj in object_list])

def update_tracks(object_list, object_positions):

    # Lots of loops, specialized processing, optimization, statistical calculations, etc.
    # Calls WrappedObject.get_position() thousands of times per frame
    ...
        ...
        pos = object_positions[obj.id]
        ...
    return object_tracks

# Call
object_list = get_objects_in_frame()
object_positions = get_object_positions()
tracks = update_tracks(object_list, object_positions)

How can this problem be approached? Both solutions work and the FP is more scalable, but what are some considerations and what is generally a good approach to determining if a group of data should be object-ized and wrapped, or if it's more scalable to build a processing framework that can be easily adjusted as more fields are added and removed?

5
  • 1
    The "FP approach" wouldn't just be to define a function that does the transformation that callers are expected to always remember to call. It would be to take that function, curry it with the get_object_positions, and return a new function that does the combination of getting positions and transforming them. In effect, this is exactly identical to your "heavy wrapper class". It's an object that does both.
    – Alexander
    Commented Aug 26, 2023 at 17:16
  • @Alexander hmm that makes sense. So I'm curious, do you have guidelines that you follow to decide between encapsulating capability in a class, vs. building a strictly functional object to achieve the same effect?
    – panlex
    Commented Aug 28, 2023 at 13:50
  • 1
    Could you elaborate on what exactly you mean by "a strictly functional object to achieve the same effect"? Are you referring to the curried function that I described? Assuming so, my answer would be "I would do as the Romans do." It would be really weird to model this computation using currying in a language like Java or Python, where that isn't the norm.
    – Alexander
    Commented Aug 28, 2023 at 18:47
  • Well here is another way to think about it. What nifty syntax tricks feed into compiler optimizations? For example chaining operations instead of performing each in isolated for-loops. While we are looking at Python many of my questions come from past Scala projects which internally called Java APIs requiring similar transformations. I made it work but it didn't feel like I reached a conclusion on this issue of encapsulated self-transformation vs externalization transforms which can be applied to other objects as they are developed.
    – panlex
    Commented Aug 31, 2023 at 13:21
  • 1
    For a sufficiently sophisticated optimizer, it won't matter. (single method) objects and closures are pretty much the same thing at an implementation level: a chunk of memory to capture state, and a pointer to a function that implements behaviour. Optimizing function currying is pretty much the same as devirtualization (replacing dynamic dispatch with static dispatch in cases where the receiver can be discovered statically). In both cases you would just end up with function calls that mutate state, which could even be inlined completely.
    – Alexander
    Commented Sep 1, 2023 at 3:35

1 Answer 1

2

There is no objective approach to make a decision like this one.

Both of your examples simply reflect different schools of thought, with some pros and cons, but nothing which cannot typically be mitigated when switching between those worlds.

So the design of such a functionality or library is mainly dependend on what the designer is used to, what they prefer, and what they know about their target audience who will use the objects. Of course, it can depend to some degree on the surroundings and contexual requirements which might give the properties you mentioned (like better scalabilty vs better encapsulation) a different weight. But at the end of the day, it remains a decision you can ask two different experts and get two different opinions.

3
  • thanks. Maybe what I'm asking since I haven't seen this explicitly, is in what situation would the supposed scalability of the functional approach be meaningful? If I were to add dozens of types that needed the transform etc. I come back to: Simple things can be functionalized, complex capabilities are better represented in encapsulated form. Seen strong tendency to use encapsulation inside scientific computing applications.
    – panlex
    Commented Aug 28, 2023 at 13:55
  • @panlex: well, you were the one who wrote "better scalability" - to be honest, I didn't really buy this. I think we would have to start by defining what kind of scalability we are talking of - scalability for using more CPUs in parallel, or scalability in terms of having more developers work in parallel on the system? I guess you mean the former.
    – Doc Brown
    Commented Aug 28, 2023 at 15:53
  • lol that is an esoteric term and I guess the latest buzzwords don't have an explicit definition. Someone should make an urbandictionary.com solution for this. Yeah I think scalability can go two ways - parallelized and distributed. Sounds like the answer is up to me as the opinionated expert.
    – panlex
    Commented Aug 31, 2023 at 13:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.