139

In other languages like Python 2 and Python 3, you can define and assign values to a tuple variable, and retrieve their values like this:

tuple = ("Bob", 24)
name, age = tuple
print(name)           #name evaluates to Bob
print(age)            #age evaluates to 24

Is there anything similar in JavaScript? Or do I just have to do it the ugly way with an array:

tuple = ["Bob", 24]
name = tuple[0]       //name Evaluates to Bob
age = tuple[1]        //age Evaluates to 24

Is there a better way to simulate Python tuples in JavaScript 5?

Update: See the answer regarding ES6, which should be favored over CoffeeScript for new projects.

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Karl
  • 6,035
  • 5
  • 30
  • 39

12 Answers12

185

JavaScript 1.7 added destructuring assignment syntax which allows you to do essentially what you are after:

function getTuple(){
   return ["Bob", 24];
}
var [a, b] = getTuple();
// a === "bob" , b === 24 are both true
Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
pc1oad1etter
  • 8,549
  • 10
  • 49
  • 64
50

You have to do it the ugly way. If you really want something like this, you can check out CoffeeScript, which has that and a whole lot of other features that make it look more like python (sorry for making it sound like an advertisement, but I really like it.)

Jay
  • 368
  • 1
  • 3
  • 14
Gabi Purcaru
  • 30,940
  • 9
  • 79
  • 95
  • Hmm, this is quite interesting, just ran up on this scenario myself, as JavaScript doesn't seem to provide obvious tuple support, and coming from an intense period of functional programming, you develop the want for tuples. I also just found this, but not sure if it works, it just looked good in terms of tuple support too: http://www.cs.umd.edu/projects/PL/arrowlets/api-tuples.xhtml. I'll definitely look at CoffeeScript. – 9codeMan9 Apr 05 '13 at 00:18
  • would be great to update this answer, lots of CoffeeScript ideas have been already port to javascript, no need anymore for CoffeeScript – ncubica Jun 17 '21 at 20:38
34

You can do something similar:

var tuple = Object.freeze({ name:'Bob', age:14 })

and then refer to name and age as attributes

tuple.name 
tuple.age 
Trevor
  • 13,085
  • 13
  • 76
  • 99
monika mevenkamp
  • 627
  • 6
  • 11
  • 6
    I won't downvote but technically this is incorrect. You can still change (ie mutate) the values of tuple.name and tuple.age after the object is declared. By definition immutable types can non be changed after they're defined. They're like read-only types where both the parameters and their values can only be declared once. – Evan Plaice Apr 28 '14 at 21:43
  • 4
    @EvanPlaice if mutability is an issue, you could use [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze), i.e.: `tuple = Object.freeze({ name:'Bob', age:14 })` – canon May 07 '14 at 16:46
  • @canon I agree, that's probably the only acceptable/correct approach in this whole thread. Unfortunately, mcm's answer doesn't freeze the object so it's still mutable. – Evan Plaice May 08 '14 at 09:24
  • 1
    @EvanPlaice I don't see where the issue of mutability came from - tuples aren't immutable in the original Python example! – Daniel Buckmaster Mar 11 '15 at 20:42
  • @DanielBuckmaster The difference between a list and a tuple in Python is, a list is mutable whereas a tuple is not. See https://docs.python.org/2/tutorial/datastructures.html#tuples-and-sequences. Tuples aren't natively supported in JavaScript because all JS data structures are mutable unless you call Object.freeze() on the object after creation. – Evan Plaice Mar 11 '15 at 22:02
  • @EvanPlaice whoah, my bad, I could have sworn tuple elements were assignable. TIL! – Daniel Buckmaster Mar 11 '15 at 22:05
  • @DanielBuckmaster Yep, and since a tuple's structure is fixed after creation their values can be accessed faster in languages that natively support them. – Evan Plaice Mar 11 '15 at 22:32
  • @evan-plaice Strings and numbers are naturally immutable in JavaScript, although I wouldn’t call them data structures – gabssnake Jan 01 '16 at 11:18
  • @gabssnake The values are, the references to those values aren't. http://stackoverflow.com/questions/8248568/javascript-numbers-immutable. An object will only be made immutable if Object.freeze() is applied. If the object has properties that are also objects you would then have to call freeze on all of the children (and recursively on all children of children) as well. An object is **not** the equivalent of a tuple. – Evan Plaice Jan 01 '16 at 18:49
  • @evan-plaice Strange first sentence. Do you disagree that strings and numbers are immutable in JavaScript ? Care to provide an example of mutation with these types ? – gabssnake Jan 02 '16 at 21:56
  • @evan-plaice I was originally commenting on your statement on mutability of *all* JS data structures – gabssnake Jan 02 '16 at 22:04
30

This "tuple" feature it is called destructuring in EcmaScript2015 and is soon to be supported by up to date browsers. For the time being, only Firefox and Chrome support it.

But hey, you can use a transpiler.

The code would look as nice as python:

let tuple = ["Bob", 24]
let [name, age] = tuple

console.log(name)
console.log(age)
Tim Perry
  • 11,766
  • 1
  • 57
  • 85
Adrian
  • 1,006
  • 2
  • 9
  • 20
  • 2
    To future readers: this feature is supported in chrome since chrome 49 (according to the Mozilla docs). You can also check compatibility using the Mozilla docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment – Jamie Apr 03 '16 at 12:45
17

A frozen array behaves identically to a python tuple:

const tuple = Object.freeze(["Bob", 24]);
let [name, age]; = tuple
console.debug(name); // "Bob"
console.debug(age); // 24

Be fancy and define a class

class Tuple extends Array { 
  constructor(...items) { 
    super(...items); 
    Object.freeze(this);
  } 
}

let tuple = new Tuple("Jim", 35);
let [name, age] = tuple;
console.debug(name); // Jim
console.debug(age); // 35
tuple = ["Bob", 24]; // no effect 
console.debug(name); // Jim
console.debug(age); // 25

Works today in all the latest browsers.

Matthew James Davis
  • 12,134
  • 7
  • 61
  • 90
  • Couldn't you just set it to a const? The const is immutable, no? – Mark A Mar 16 '18 at 00:50
  • 5
    No. The const is not reassignable. You can't do const tuple = ["Jim", 35]; tuple = ["James", 35]. You can do const tuple = ["Jim", 35]; tuple[0] = "James"; Therefore it is not immutable. – Matthew James Davis Oct 12 '18 at 01:49
8

Tuples aren't supported in JavaScript

If you're looking for an immutable list, Object.freeze() can be used to make an array immutable.

The Object.freeze() method freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed. In essence the object is made effectively immutable. The method returns the object being frozen.

Source: Mozilla Developer Network - Object.freeze()

Assign an array as usual but lock it using 'Object.freeze()

> tuple = Object.freeze(['Bob', 24]);
[ 'Bob', 24 ]

Use the values as you would a regular array (python multi-assignment is not supported)

> name = tuple[0]
'Bob'
> age = tuple[1]
24

Attempt to assign a new value

> tuple[0] = 'Steve'
'Steve'

But the value is not changed

> console.log(tuple)
[ 'Bob', 24 ]
Evan Plaice
  • 13,944
  • 6
  • 76
  • 94
  • On a side-note hopefully Tuples will get first-class support in ES6. True native tuple (ie heterogenous sequence) will also improve speed. – Evan Plaice Mar 11 '15 at 22:39
5

Unfortunately you can't use that tuple assignment syntax in (ECMA|Java)Script.

EDIT: Someone linked to Mozilla/JS 1.7 - this wouldn't work cross-browser but if that is not required then there's your answer.

meder omuraliev
  • 183,342
  • 71
  • 393
  • 434
4

This is not intended to be actually used in real life, just an interesting exercise. See Why is using the JavaScript eval function a bad idea? for details.

This is the closest you can get without resorting to vendor-specific extensions:

myArray = [1,2,3];
eval(set('a,b,c = myArray'));

Helper function:

function set(code) {
    var vars=code.split('=')[0].trim().split(',');
    var array=code.split('=')[1].trim();
    return 'var '+vars.map(function(x,i){return x+'='+array+'['+i+']'}).join(',');
}

Proof that it works in arbitrary scope:

(function(){
    myArray = [4,5,6];
    eval(set('x,y,z = myArray'));
    console.log(y);  // prints 5
})()

eval is not supported in Safari.

Community
  • 1
  • 1
ninjagecko
  • 88,546
  • 24
  • 137
  • 145
4

As an update to The Minister's answer, you can now do this with es2015:

function Tuple(...args) {
  args.forEach((val, idx) => 
    Object.defineProperty(this, "item"+idx, { get: () => val })
  )
}


var t = new Tuple("a", 123)
console.log(t.item0) // "a"
t.item0 = "b"
console.log(t.item0) // "a"

https://jsbin.com/fubaluwimo/edit?js,console

Zach Dahl
  • 609
  • 5
  • 12
  • There is no reason why you couldn't do this before ES2015... Also this does not answer the OP's question, he asked for destructuring – assembly_wizard Nov 20 '16 at 16:40
3

You can have a tuple type in Javascript as well. Just define it with higher order functions (the academic term is Church encoding):

const Tuple = (...args) => {
  const Tuple = f => f(...args);
  return Object.freeze(Object.assign(Tuple, args));
};

const get1 = tx => tx((x, y) => x);

const get2 = tx => tx((x, y) => y);

const bimap = f => g => tx => tx((x, y) => Tuple(f(x), g(y)));

const toArray = tx => tx((...args) => args);

// aux functions

const inc = x => x + 1;
const toUpperCase = x => x.toUpperCase();

// mock data

const pair = Tuple(1, "a");

// application

console.assert(get1(pair) === 1);
console.assert(get2(pair) === "a");

const {0:x, 1:y} = pair;
console.log(x, y); // 1 a

console.log(toArray(bimap(inc) (toUpperCase) (pair))); // [2, "A"]

const map = new Map([Tuple(1, "a"), Tuple(2, "b")]);
console.log(map.get(1), map.get(2)); // a b

Please note that Tuple isn't used as a normal constructor. The solution doesn't rely on the prototype system at all, but solely on higher order functions.

What are the advantages of tuples over Arrays used like tuples? Church encoded tuples are immutable by design and thus prevent side effects caused by mutations. This helps to build more robust applications. Additionally, it is easier to reason about code that distinguishes between Arrays as a collection type (e.g. [a]) and tuples as related data of various types (e.g. (a, b)).

1

Here is a simple Javascript Tuple implementation:

var Tuple = (function () {
   function Tuple(Item1, Item2) {
      var item1 = Item1;
      var item2 = Item2;
      Object.defineProperty(this, "Item1", {
          get: function() { return item1  }
      });
      Object.defineProperty(this, "Item2", {
          get: function() { return item2  }
      });
   }
   return Tuple;
})();

var tuple = new Tuple("Bob", 25); // Instantiation of a new Tuple
var name = tuple.Item1; // Assignment. name will be "Bob"
tuple.Item1 = "Kirk"; // Will not set it. It's immutable.

This is a 2-tuple, however, you could modify my example to support 3,4,5,6 etc. tuples.

Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
  • Actually this is not a t-uple but a pair. – Pier Paolo Ramon Dec 17 '14 at 13:25
  • The `tuple` instance is still mutable, so it's technically not a tuple. For proof change `tuple.Item1 = "Steve"` then `console.log()` the output. – Evan Plaice Mar 11 '15 at 22:35
  • 1
    Thanks for finding that. I have modified the example to make the Tuple immutable. – Faris Zacina Mar 12 '15 at 12:08
  • How do you modify this to support an arbitrary length? – aij Oct 07 '16 at 15:21
  • This does not answer the OP's question, he asked for destructuring – assembly_wizard Nov 20 '16 at 16:42
  • @ThatWeirdo.. no.. he hasn't asked about destructuring, but destructuring is a possible efficient solution to his problem. There are x possible solutions and every is valid, even though they vary in conciseness/simplicity – Faris Zacina Nov 21 '16 at 07:06
  • @The Minister Re-read the question. OP was searching for a syntax like `name, age = tuple` like Python has, to not "do it in the ugly way" (OP quote). This answer does not provide such a thing, it does provides a tuple look-alike, without the syntax the OP asked for, so it does not answer the question. The question was "simulate Python tuples in JS5", and this by no means looks like a Python tuple, it is a Python dict at most – assembly_wizard Nov 21 '16 at 09:38
  • @ThatWeirdo that sounds contradictory. So he asked about a python-tuple simulation but its not satisfactory to provide a tuple lookalike? I think its useful to have this option too, at least for comparison of different solutions, even if its sub-optimal. – Faris Zacina Nov 21 '16 at 10:02
1

I made a tuple implementation that works quite well. This solution allows for array destructuring, as well as basic type-cheking.

const Tuple = (function() {
    function Tuple() {
        // Tuple needs at least one element
        if (arguments.length < 1) {
            throw new Error('Tuple needs at least one element');
        }

        const args = { ...arguments };

        // Define a length property (equal to the number of arguments provided)
        Object.defineProperty(this, 'length', {
            value: arguments.length,
            writable: false
        });

        // Assign values to enumerable properties
        for (let i in args) {
            Object.defineProperty(this, i, {
                enumerable: true,
                get() {
                    return args[+i];
                },
                // Checking if the type of the provided value matches that of the existing value
                set(value) {
                    if (typeof value !== typeof args[+i]) {
                        throw new Error('Cannot assign ' + typeof value + ' on ' + typeof args[+i]);
                    }

                    args[+i] = value;
                }
            });
        }

        // Implementing iteration with Symbol.iterator (allows for array destructuring as well for...of loops)
        this[Symbol.iterator] = function() {
            const tuple = this;

            return {
                current: 0,
                last: tuple.length - 1,
                next() {
                    if (this.current <= this.last) {
                        let val = { done: false, value: tuple[this.current] };
                        this.current++;
                        return val;
                    } else {
                        return { done: true };
                    }
                }
            };
        };

        // Sealing the object to make sure no more values can be added to tuple
        Object.seal(this);
    }

    // check if provided object is a tuple
    Tuple.isTuple = function(obj) {
        return obj instanceof Tuple;
    };

    // Misc. for making the tuple more readable when printing to the console
    Tuple.prototype.toString = function() {
        const copyThis = { ...this };
        const values = Object.values(copyThis);
        return `(${values.join(', ')})`;
    };

    // conctat two instances of Tuple
    Tuple.concat = function(obj1, obj2) {
        if (!Tuple.isTuple(obj1) || !Tuple.isTuple(obj2)) {
            throw new Error('Cannot concat Tuple with ' + typeof (obj1 || obj2));
        }

        const obj1Copy = { ...obj1 };
        const obj2Copy = { ...obj2 };

        const obj1Items = Object.values(obj1Copy);
        const obj2Items = Object.values(obj2Copy);

        return new Tuple(...obj1Items, ...obj2Items);
    };

    return Tuple;
})();

const SNAKE_COLOR = new Tuple(0, 220, 10);

const [red, green, blue] = SNAKE_COLOR;
console.log(green); // => 220


uranshishko
  • 312
  • 1
  • 7