Support for arrays in `startsWith` (and, maybe, `endsWith`)

**Use case**

Would be quite useful for handling tree-like structured data where the node paths are specified as arrays of names or IDs.

**Describe the solution you'd like**

The desired behavior would be like this:
```
startsWith([1, 2, 3, 4], [1, 2])  -- true (1)
startsWith(['qwe', 'rty', 'ui', 'op'], ['qwe', 'rty'])  -- true (1)
startsWith([1, 2, 3, 4], [2, 4])  -- false (0)
startsWith([1, 1, 2, 2], [1, 2])  -- false (0)
```

And similarly, for `endsWith`

**Describe alternatives you've considered**

Something like `arraySlice(first, 1, length(second)) = second`

But `startsWith` would be much more readable, harder to make a mistake in and, probably, can be better optimized (i.e. might not have to create new objects like `arraySlice` does)
{
    sink.elements.resize(sink.current_offset + 1);
    Field field;
    slice.elements->get(slice.position, field);
    sink.elements.push_back(applyVisitor(FieldVisitorConvertToNumber<T>(), field));
    ++sink.current_offset;
}
template <typename T>
inline ALWAYS_INLINE void writeSlice(const NumericValueSlice<T> & slice, GenericArraySink & sink)
{
    Field field = T(slice.value);
    sink.elements.insert(field);
    ++sink.current_offset;
}
template <typename SourceA, typename SourceB, typename Sink>
void NO_INLINE concat(SourceA && src_a, SourceB && src_b, Sink && sink)
{
    sink.reserve(src_a.getSizeForReserve() + src_b.getSizeForReserve());
    while (!src_a.isEnd())
    {
        writeSlice(src_a.getWhole(), sink);
        writeSlice(src_b.getWhole(), sink);
        sink.next();
        src_a.next();
        src_b.next();
    }
}
template <typename Source, typename Sink>
void concat(const std::vector<std::unique_ptr<IArraySource>> & array_sources, Sink && sink)