static DataFrame RecJoin <T1, T2>(IDataFrameView left, IDataFrameView right, int[] icolsLeft, int[] icolsRight,
                                          string leftSuffix     = null, string rightSuffix      = null,
                                          JoinStrategy joinType = JoinStrategy.Inner, bool sort = true)
            where T1 : IEquatable <T1>, IComparable <T1>
            where T2 : IEquatable <T2>, IComparable <T2>
        {
            var kind = left.Kinds[icolsLeft[2]];

            if (icolsLeft.Length == 3)
            {
                switch (kind)
                {
                case DataKind.BL: return(left.TJoin <T1, T2, DvBool>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.I4: return(left.TJoin <T1, T2, DvInt4>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.U4: return(left.TJoin <T1, T2, uint>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.I8: return(left.TJoin <T1, T2, DvInt8>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.R4: return(left.TJoin <T1, T2, float>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.R8: return(left.TJoin <T1, T2, double>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.TX: return(left.TJoin <T1, T2, DvText>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                default:
                    throw new NotImplementedException($"Join is not implemented for type '{kind}'.");
                }
            }
            else
            {
                throw new NotImplementedException($"Join is not implemented for {icolsLeft.Length} columns.");
            }
        }
        static DataFrame RecJoin(IDataFrameView left, IDataFrameView right, int[] icolsLeft, int[] icolsRight,
                                 string leftSuffix     = null, string rightSuffix      = null,
                                 JoinStrategy joinType = JoinStrategy.Inner, bool sort = true)
        {
            var kind = left.Kinds[icolsLeft[0]];

            if (icolsLeft.Length == 1)
            {
                switch (kind)
                {
                case DataKind.BL: return(left.TJoin <DvBool>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.I4: return(left.TJoin <DvInt4>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.U4: return(left.TJoin <uint>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.I8: return(left.TJoin <DvInt8>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.R4: return(left.TJoin <float>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.R8: return(left.TJoin <double>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.TX: return(left.TJoin <DvText>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                default:
                    throw new NotImplementedException($"Join is not implemented for type '{kind}'.");
                }
            }
            else
            {
                switch (kind)
                {
                case DataKind.BL: return(RecJoin <DvBool>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.I4: return(RecJoin <DvInt4>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.U4: return(RecJoin <uint>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.I8: return(RecJoin <DvInt8>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.R4: return(RecJoin <float>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.R8: return(RecJoin <double>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                case DataKind.TX: return(RecJoin <DvText>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                default:
                    throw new NotImplementedException($"Join is not implemented for type '{kind}'.");
                }
            }
        }
        static DataFrame RecJoin <T1>(IDataFrameView left, IDataFrameView right, int[] icolsLeft, int[] icolsRight,
                                      string leftSuffix     = null, string rightSuffix      = null,
                                      JoinStrategy joinType = JoinStrategy.Inner, bool sort = true)
            where T1 : IEquatable <T1>, IComparable <T1>
        {
            var kind = left.Kinds[icolsLeft[1]];

            if (icolsLeft.Length == 2)
            {
                if (kind.IsVector())
                {
                    throw new NotImplementedException();
                }
                else
                {
                    switch (kind.RawKind())
                    {
                    case DataKind.Boolean: return(left.TJoin <T1, bool>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Int32: return(left.TJoin <T1, int>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.UInt32: return(left.TJoin <T1, uint>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Int64: return(left.TJoin <T1, long>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Single: return(left.TJoin <T1, float>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Double: return(left.TJoin <T1, double>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.String: return(left.TJoin <T1, DvText>(right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    default:
                        throw new NotImplementedException($"Join is not implemented for type '{kind}'.");
                    }
                }
            }
            else
            {
                if (kind.IsVector())
                {
                    throw new NotImplementedException();
                }
                else
                {
                    switch (kind.RawKind())
                    {
                    case DataKind.Boolean: return(RecJoin <T1, bool>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Int32: return(RecJoin <T1, int>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.UInt32: return(RecJoin <T1, uint>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Int64: return(RecJoin <T1, long>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Single: return(RecJoin <T1, float>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.Double: return(RecJoin <T1, double>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    case DataKind.String: return(RecJoin <T1, DvText>(left, right, icolsLeft, icolsRight, leftSuffix, rightSuffix, joinType, sort));

                    default:
                        throw new NotImplementedException($"Join is not implemented for type '{kind}'.");
                    }
                }
            }
        }