protected static function equal($a, $b, $aStack, $bStack)
    {
        // Unwrap any wrapped object.
        $a instanceof self && $a = $a->_wrapped;
        $b instanceof self && $b = $b->_wrapped;

        if ($a === null && $b === null)
            return true;

        // Two resources are always considered different since there is no way to compare them
        if (is_resource($a) && is_resource($b))
            return false;

        // Perform classic scalar comparison.
        if (is_scalar($a) && is_scalar($b))
            return $a == $b;

        // Assume equality for cyclic structures. The algorithm for detecting cyclic structures is adapted from
        // ES 5.1 section 15.12.3, abstract operation JO.
        $length = count($aStack);
        while ($length--)
            if ($aStack[$length] == $a)
                return $bStack[$length] == $b;

        // Add the first object to the stack of traversed objects.
        $aStack[] = $a;
        $bStack[] = $b;

        $result = true;
        $size = 0;

        if (static::isTraversable($a) && static::isTraversable($b)) {
            // Try to deep compare sequences.
            static::each($a, function ($item, $key) use (& $a, & $b, & $aStack, & $bStack, & $size, & $result) {
                $size++;

                $result = static::has($b, $key) && static::equal(
                    static::get($a, $key),
                    static::get($b, $key),
                    $aStack,
                    $bStack
                );

                if (!$result)
                    return static::BREAKER;
            });

            if ($size != static::size($b))
                $result = false;
        }
        else {
            // Last hope comparison.
            $result = $a == $b;
        }

        // Remove the first object from the stack of traversed objects.
        array_pop($aStack);
        array_pop($aStack);

        return $result;
    }