"Just use maps" in Java?

Quoting Alan Perlis: “It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.” Perlisisms - "Epigrams in Programming" by Alan J. Perlis

I know this quote of Alan Perlis but I don’t see how it applies to procedures and structs. To me, it applies when we adhere to DOP Principle #2

Structs are not generic data structures: you have to define a struct for a player, a struct for a tank, a struct for a position etc…

No, but it gets complicated, and I’m not as familiar with the procedural style, so if anyone wants to correct me go ahead.

A procedure has inputs and a return value, can be called arbitrarily to run, and is allowed to do side-effects. It differs from an impure function in that it isn’t first-class, you can’t create them at run-time, pass them around as arguments to other procedures, have them close over environment variables, etc., where as you can with an impure function.

As opposed to methods, procedures are not tied to any particular set of data. They can access any data in scope of them, as well as their inputs. Other then that, methods and procedures are the same.

In procedural programming, you can structure your data in heterogeneous containers called structs or in arrays of homogeneous data.

So a struct is not very flexible, it is static in its structure, you can’t combine two of them together, or do any kind of set operations on them, etc.

Where it gets complicated is that normally procedural languages are missing a lot of other things you’d need to make structs flexible in their use. C is kind of the biggest procedural language, and it doesn’t support union types, so you actually can’t do what you can in Clojure in C, because the procedure is itself very static.

struct Player {
  int health;
  int x;
  int y;
};

struct Tank {
  int x;
  int y;
};

void movePlayer(struct Player *player, int x, int y) {
   player->x = x;
   player->y = y;
}

Now the problem is that the movePlayer function needs to know the type of struct in order to know the memory layout for it to change the value of x and y. The two structs have different memory layouts, so you can’t do the same thing you do for one on the other.

That’s because a struct is very very simple and static like I said, its like a fixed size array where the struct definition defines the start index and end index of each field.

But, I don’t know if this is a restriction of the procedural paradigm? Or if its more that no procedural language with more advanced features have been designed. What you would need is a generic procedure, something like:

void movePlayer<T>(struct T *m, int x, int y) {
   m->x = x;
   m->y = y;
}

So I could imagine a procedural language that supports that, at which point, ya in this case it would be more flexible than OO.

That said, there’s many other issues with procedural languages, and some parts are less flexible than OO, so even with such generic procedures, I think I’d say OO is more flexible overall. The big one being that mutation is very risky if unconstrained, which is where OO’s encapsulation of mutable data behind methods are really worth their salt.

Not sure what you mean here because C definitely does have a union construct that allows you to declare overlapping memory layouts.

In fact, if you declared your Player struct so the position was first, you could have a union that overlapped Player and Tank and could access x or y from either type using that union.

struct Player {
  int x;
  int y;
  int health;
};

struct Tank {
  int tx;
  int ty;
};

union Movable {
  struct Player p;
  struct Tank t;
};

void moveThing( void* thing, int x, int y ) {
  ((Movable*)thing)->x = x;
  ((Movable*)thing)->y = y;
}

And it doesn’t even matter that the fields have different names between Player and Tank at this point: all that matters is the memory layout matches for those first two int fields.

I’m not claiming this is portable or even particularly safe, and I definitely wouldn’t claim it is “good practice” but this sort of overlapping of memory structures is often very necessary when you are doing low-level systems programming in C.

3 Likes

Ah great, my C is amateur at best. That’s exactly the kind of correction I was hoping to get.

1 Like

I think it depends a bit on whose terminology you’re using. IIRC, in Pascal, for example (I guess I’m showing my age here), procedures do not have return values, but rather can return values via reference arguments. I guess in C the equivalent would be something returning void. As opposed to functions, which actually have a return type. Note that this is orthogonal to purity and referential transparency.

1 Like

New food for thought: a Java library named Paguro that embraces DOP.

Paguro simplifies a lot how to write code that manipulates data in Java.

Please share your thoughts about “Just use maps” with Paguro.

For instance, the following piece of Clojure code that turns a nested collection inside out to procuce a map

(def emails
  [["Fred" ["fred@gmail.com", "fred@hello.com"]]
   ["Jane" ["jane@gmail.com", "jane@hello.com"]]])

(->> (mapcat (fn [[person emails]]
          (map (fn [email]
                 [email person])
                 emails))
        emails)
     (into {})) 

;; {"fred@gmail.com" "Fred",
;;  "fred@hello.com" "Fred",
;;  "jane@gmail.com" "Jane", 
;;  "jane@hello.com" "Jane"}

is written like this with Paguro

vec(tup("Fred", vec("fred@gmail.com", "fred@hello.com")),
    tup("Jane", vec("jane@gmail.com", "jane@hello.com")))
    .flatMap(person -> person._2()
             .map(email -> tup(email, person._1())))
    .toImMap(x -> x)

// PersistentHashMap(Tuple2("fred@hello.com","Fred"),Tuple2("jane@hello.com","Jane"),Tuple2("fred@gmail.com","Fred"),Tuple2("jane@gmail.com","Jane"))

Want more? Take a look at detailed examples in Paguro GitHub repo.

Or play with a Live example here.

It does seem better than plain java, but it is so noisy compared to Clojure!

No one doubt that Clojure is the best language for DOP.

The question is: is Java DOP (with Paguro) better than classic OOP Java?

I suspect you’ll still end up fighting with Java’s OOP-ness in any case

With all due respect, The habit of “We just use maps!" is too easily abused, e.g. people try to keep using “maps of vectors of maps” to a extend where it doesn’t make sense anymore, e.g. places where it is better to use a graph structure like datascript, asami, etc.

I would agree that it’s best to keep the maps shallow, but I think that the depth of them is not really what’s being discussed, but rather the uniformity of accessing/operating on them, vs the specificity of Java classes for every domain object.

In some situations, deeply nested maps do map well to the domain, and though they’re a bit more painful to use, they can work quite well (with judicious use of Specter, Meander, or similar)

1 Like

I am inviting you guys to continue this exciting discussion on another thread It is possible to “Just use maps” in Java!

1 Like

This article summarises my understanding about how to apply Data-Oriented programming in Java.

1 Like

For interested readers, this is a draft on the possible syntax for with: amber-docs/eg-drafts/reconstruction-records-and-classes.md at master · openjdk/amber-docs · GitHub

2 Likes

It’s interesting to see how many complex additions to the language we need to benefit from something as simple as assoc in a language where data is represented with generic immutable maps!

Can you do that in Clojure, where the code is data?

I wouldn’t say that in Clojure “code is data”.
I would day “code is represented with data structures”

I think that’s what I thought when I first encountered Lisp. Everything is a list and it could be a list of either code or data but what about when code changes code? Has the code being changed flipped to be data? Only if it is no longer executable.

Programming is data processing, so a program must define the (initial state) process. The code/process is a special kind of data about how to do something: imperative rather than declarative data. It can clearly move between two states but it’s “made of the same stuff”, like ice or liquid water.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.