Pair

A Pair is a container for exactly two objects. Since Sidef is dynamically typed, its elements may be any type, including other Pairs.

This eliminates the need for a separate LinkedList class, as nested Pairs serve the same purpose.

If a Pair is created missing one or both elements, that element will be nil.

The literal syntax for creating Pairs is the : method on Objects, which is not an operator, and therefore cannot have a nil left-side.

"paired":"strings"        # -> Pair("paired", "strings")
Pair("paired", "strings") # -> ==/==

"a":nil                   # -> Pair("a", nil)
nil:"b"                   # -> error, methods cannot be called on undefined values
Pair(nil, "b")            # -> Pair(nil, "b")

Note that the Pair literal syntax does not round-trip, i.e Pair.to_s will never give the : representation.

When using the Pair literal syntax with variable names, this overlaps with the syntax for Complex numbers and NamedParams.

var (a, b) = ("a", "b")
say a:b                 # prints "a:"
say a:b.is_a(Pair)      # prints false
                        # it has type NamedParam

var (c, d) = (1, 2)     
say c:d                 # prints 1+2i
say c:d.is_a(Pair)      # prints false
say c:d.class           # prints Complex

When the left-hand side is not a number, force evaluation of : as a method with . or \:

say a.:b                # prints Pair("a", "b")
say a\:b                # ==/==
say (a : b)             # ==/==
say Pair(a, b)          # ==/==

say c.:d                # prints 1+2i
say c\:d                # ==/==
say (c : d)             # ==/==
say Pair(c, d)          # prints Pair(1, 2)

Note this still calls the : method on numbers, converting its arguments to a Complex, so stick with the Pair() constructor if the types are uncertain.

Indexing

Pair objects can be indexed directly, but also define the first / key and second / value methods:

var pair = Pair(1, 2)
say pair.first        # prints 1
say pair.key          # ==/==
say pair[0]           # ==/==
say pair.second       # prints 2
say pair.value        # ==/==
say pair[1]           # ==/==

say pair[2]           # prints nil
say pair[4..10]       # a range of nils

Like other iterable and indexable objects, accessing a nonexistent Pair element gives nil.

Nesting

To use nested pairs like a Lisp-style linked list, the second or value methods can be chained, as long as the second element is always a Pair.

The : method can be combined with the infix : operator, which symbol-ifies its operand, to easily write chains and linked lists of Strings.

:a                                # -> "a", the named variable a
:a: :chain: :of: :string: :pairs  # -> Pair("a", Pair("chain", Pair("of", Pair("string", "pairs"))))
:a::chain::of::string::pairs      # -> ==/==
:a::chain::of::string::pairs:     # -> Pair(String, NamedParam)

Including a trailing : in such chains will cause the rest of the expression to be parsed as a NamedParam.

To make this a (old-style) linked list, we should be able to find the end, thus include a trailing nil (or null, or false...). Then we can traverse it, destructively for brevity:

var list = :a: :linked: :list: :of: :pairs: :of: :strings: nil

var copy = list.dclone

# imperative style
loop {
  print copy.key+" "
  break if (! copy.value!)
}

# prints: a linked list of pairs of strings
# copy is now nil, the terminating element

# functional style
func traverse (list) {
  print list.key+" "
  __FUNC__(list) if list.value!
}

traverse(list.dclone)

# prints: a linked list of pairs of strings

The swap method can be used to transpose the first and second elements:

(:a : :b)       # -> Pair("a", "b")
(:a : :b).swap  # -> Pair("b", "a")