For example Null can be represented as empty string

```sql
select arrayStringConcat(['x',Null], ';');

┌─arrayStringConcat(['x', Null], ';')─┐
│ x;                                  │
└─────────────────────────────────────┘
```

But mostly it for avoiding a cast in case of Nullable Strings
```
SELECT arrayStringConcat(col0, ';')
FROM
(
    SELECT [toNullable('x')] AS col0
) AS t
```

Also I think arrayStringConcat can support any types not only Strings, it allows to avoid explicit casts.

```
SELECT arrayStringConcat(col0, ';')
FROM
(
    SELECT [1, 2, 3] AS col0
) AS t
```
{
public:
    static constexpr auto name = Generator::name;
    static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionTokens>(); }
    String getName() const override
    {
        return name;
    }
    bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & ) const override { return true; }
    size_t getNumberOfArguments() const override { return Generator::getNumberOfArguments(); }
    DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
    {
        Generator::checkArguments(arguments);
        return std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>());
    }
    ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t ) const override
    {
        Generator generator;
        generator.init(arguments);
        const auto & array_argument = arguments[generator.getStringsArgumentPosition()];
        const ColumnString * col_str = checkAndGetColumn<ColumnString>(array_argument.column.get());
        const ColumnConst * col_const_str =