Skip to content

Instantly share code, notes, and snippets.

@johnkary
Last active August 29, 2015 14:16
Show Gist options
  • Save johnkary/a64363bac55c90a1ca97 to your computer and use it in GitHub Desktop.
Save johnkary/a64363bac55c90a1ca97 to your computer and use it in GitHub Desktop.
Example of using tuples instead of array key-value pairs
class BuildReport
{
private function getColumnHeaders()
{
// Why do you need array keys if this array is private details?
// It's never exposed outside this class so you have full control of its format
return [
['id' => 'first_name', 'label' => 'First Name'],
['id' => 'last_name', 'label' => 'Last Name'],
['id' => 'department', 'label' => 'Department'],
];
}
public function render($records)
{
foreach ($this->getColumnHeaders() as $header) {
$id = $header['id'];
$label = $header['label'];
// Do something with $id and label ...
}
}
}
class BuildReport
{
private function getColumnHeaders()
{
// If you need to change the data, the changes are confined to this class
return [
['first_name', 'First Name'],
['last_name', 'Last Name'],
['department', 'Department'],
];
}
public function render($records)
{
// PHP >= 5.5 -- This is the shortest thanks to this feature in 5.5 https://wiki.php.net/rfc/foreachlist
foreach ($this->getColumnHeaders() as list($id, $label)) {
// Do something with $id and label ...
}
// PHP < 5.5 -- This is slightly more code if you're running 5.4 or older
foreach ($this->getColumnHeaders() as $header) {
list($id, $label) = $header;
// Do something with $id and label ...
}
}
}
@darrencauthon
Copy link

I'd say the tuples are ok... but only if render is under test.

And not because I'm not anal about testing. I think you're replacing an overly expressive implementation with one that's missing some meaning. $header['id'] and $header['label'] stick out much better than list($id, $label). I'm ok giving up some expression in the production code if there's an expressive test... a test that says "the id is shown" and "the label is shown".

@johnkary
Copy link
Author

@darrencauthon You raise two separate points: 1) Preferences when reading code; 2) Ensure code works as expected.

Point 1, it's much easier for me to see the important work code is doing when there's less cruft and boilerplate. I despise ceremonious setup code mixed with behavior code--both in production code and in test code. (Think of the pain you feel when duplicating setup within test method code, instead of keeping it in a setUp() like method.) The less setup code, and the less parsing I have to do of boilerplate code of unpacking/packing data to get it in the right format to work with it, the better.

Point 2, ensuring this code works, would be covered by a test case that exercises its public methods. A good test, one that's ignorant of internal implementation, wouldn't care if internally I used tuples or hashes or whatever else. I don't see a difference in critical need to test this code vs any other unit in my system. Tuples work great here because they're private to the unit. If I were exposing these as a message between two objects in PHP I'd probably use a hash, like the key-pair example, or a value object if the properties were numerous.

@darrencauthon
Copy link

I don't know how to do this in PHP, but in Ruby I sometimes do this:

[
  ['John',   'Galt'],
  ['Howard', 'Roark'],
].map { |x| { first_name: x[0], last_name: x[1] } }

Because I totally agree with you, I hate reading the same hash labels over and over and over. But I also love easy-to-read hashes. 😄

On your point (1), I think I agree with you in this case, seeing that the method is "render" and on the edges of your app. If this was something deeper in the app, like something with business logic, I'd want the "id" and "label" to be obvious to the reader.

On point (2), it's hard to get into a full example here, but sometimes it's easier to describe the "flow" than the bits. Like, I might have an integration where I have to verify that a dozen fields are carried from System A to System B. I might start with the key=>value mapping, twelve lines of boiler-plate code. But then I might see the "flow" and refactor into something where there were no keys... there was just data being passed. But that sort of code can be pretty magical and hard to understand, so I'd keep the twelve tests to express to the other programmer, in some way, that yes.. I am copying id, first_name, last_name, city, etc between the two systems.

@johnkary
Copy link
Author

@darrencauthon My implementation in the gist above is PHP's shortest possible way to do what your Ruby example did. Using PHP, and in this use case, I don't see a benefit to using map.

For your reference, the equivalent PHP using map to your Ruby example is shown below. I think you'll agree it's much more difficult to visually parse. Watch it execute here. Code:

<?php

// Example 1 -- Unpacking the array to do work on it
array_map(function ($x) {
    $first_name = $x[0];
    $last_name = $x[1];

    // Do important work here with first_name and last_name...
    // But $first_name and $last_name are scoped to this function

    // See the second example for how we'd inject an anonymous function to operate with $first_name and $last_name
}, [
    ['John',  'Galt'],
    ['Howard', 'Roark'],
]);


// Example 2 -- Separation of input setup and behavior
$behavior = function ($first_name, $last_name) {
    var_dump('Doing hard work!', $first_name, $last_name);
};

array_map(function ($x) use ($behavior) {
    $first_name = $x[0];
    $last_name = $x[1];

    // $behavior is invoked using our unpacked arguments
    $behavior($first_name, $last_name);
}, [
    ['John',  'Galt'],
    ['Howard', 'Roark'],
]);

@darrencauthon
Copy link

Ah yikes, list away! ⛵ 😄

@archwisp
Copy link

This is what I was talking about on Twitter.

class BuildReport
{
    private function getColumnHeaders()
    {
        // If this method is private and consiming functions are expected
        // to know about its structure, why not do this and save the
        // consuming functions the work of local assignments.
        return [
            (object)['id' => 'first_name', 'label' => 'First Name'],
            (object)['id' => 'last_name',  'label' => 'Last Name'],
            (object)['id' => 'department', 'label' => 'Department'],
            ];
    }

    public function render($records)
    {
        foreach ($this->getColumnHeaders() as $header) {
            // Do something with $header->id and $header->label ...
        }
    }
}

@johnkary
Copy link
Author

@archwisp $header->id isn't bad but is equal in readability for me compared to $header['id']. I'm not sure on the performance impact from casting an array to an object, but doing so seems unnecessary if we're not gaining in readability inside our foreach.

@jrmadsen67
Copy link

Perhaps this is a bad example, but what do you do when you later on you need to add meta-data to each of these? ex: a class name to certain headers

It seems to me the weakness of a tuple is that by its very definition it is hard-coded to be a pair. I ccan see other examples where this could be desirable - key:values where you might actually like to enforce that pairing, but if that is not the case, you seem to lock yourself into a structure purely because you feel it "looks like nicer code"

Have I completely missed your point?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment