Ship

Book · Tuples

Tuples

Draft

Group and unpack a fixed number of values.

On this page · 5 sections

Values and types

Tuple values and types use parenthesized commas: (Int, String) and (x, y). A tuple always has at least two fields; trailing commas are accepted.

let pairpair: fn() -> (Int, String) = () -> (Int, String) => (77: Int, "ship""ship": String);

Unpacking

let unpacks nested tuples the same way.

Nested Tuples

test✓
  let (countcount: Int, labellabel: String) = pairpair: fn() -> (Int, String)();
  expect ✓countcount: Int == 77: Int;
  expect ✓labellabel: String == "ship""ship": String;

  let (firstfirst: Int, (secondsecond: Int, thirdthird: Int)) = (11: Int, (22: Int, 33: Int));
  expect ✓firstfirst: Int == 11: Int;
  expect ✓secondsecond: Int == 22: Int;
  expect ✓thirdthird: Int == 33: Int;

  let (prefixprefix: Int, ..) = (44: Int, 55: Int, 66: Int);
  expect ✓prefixprefix: Int == 44: Int;

Labels

~name: value labels a tuple field. Labels are semantic and stay attached to their physical positions, so changing their order changes the type. ~name puns ~name: name.

type Bounds = (min: Int, max: Int)

let boundsbounds: fn(min: Int, max: Int) -> (min: Int, max: Int) = (minmin: Int, maxmax: Int) -> Bounds => (~minmin: Int, ~maxmax: Int);

Labelled Returns

test✓
  let resultresult: (min: Int, max: Int) = boundsbounds: fn(min: Int, max: Int) -> (min: Int, max: Int)(22: Int, 99: Int);
  ✓resultresult: (min: Int, max: Int): Bounds;

  let (~minmin: Int, ~maxmax: Int) = resultresult: (min: Int, max: Int);
  expect ✓(minmin: Int, maxmax: Int) == (22: Int, 99: Int);

Labels may be mixed with positional fields. Construction order remains runtime order.

type Scan = (value: String, Int, after: Int)

let scanscan: fn(value: String, width: Int, after: Int) -> (value: String, Int, after: Int) = (valuevalue: String, widthwidth: Int, afterafter: Int) -> Scan => (~valuevalue: String, widthwidth: Int, ~afterafter: Int);

Mixed Labels

test✓
  let resultresult: (value: String, Int, after: Int) = scanscan: fn(value: String, width: Int, after: Int) -> (value: String, Int, after: Int)("ship""ship": String, 44: Int, 88: Int);
  let (~valuevalue: String, widthwidth: Int, ~afterafter: Int) = resultresult: (value: String, Int, after: Int);
  expect ✓(valuevalue: String, widthwidth: Int, afterafter: Int) == ("ship""ship": String, 44: Int, 88: Int);

Labelled patterns

A known tuple type lets patterns name fields in a different order or omit fields with ... These are compile-time label lookups; the runtime tuple remains positional.

Labelled Patterns

test✓
  let resultresult: (min: Int, max: Int) = boundsbounds: fn(min: Int, max: Int) -> (min: Int, max: Int)(33: Int, 1111: Int);
  let (~maxmax: Int, ~minmin: Int) = resultresult: (min: Int, max: Int);
  expect ✓(minmin: Int, maxmax: Int) == (33: Int, 1111: Int);

  let (~max: upperupper: Int, ..) = resultresult: (min: Int, max: Int);
  expect ✓upperupper: Int == 1111: Int;

Products

Product let patterns bind fields by name.

type User = {
  username: String;
  contact_email: String;
}

Product Fields

test✓
  let useruser: Book.LanguageReference.Tuples.User = { username = "Teej""Teej": String; contact_email = "teej@example.com""teej@example.com": String; };
  let { usernameusername: String; contact_emailcontact_email: String; } = useruser: Book.LanguageReference.Tuples.User;
  expect ✓usernameusername: String == "Teej""Teej": String;
  expect ✓contact_emailcontact_email: String == "teej@example.com""teej@example.com": String;