# FrankenTuples.jl

The FrankenTuples package defines a type, `FrankenTuple`

, which is a creature not unlike Frankenstein's monster. It is comprised of both a `Tuple`

and a `NamedTuple`

to facilitate situations in which some but not all elements of a tuple are named, e.g. `(1, 2; a=3, b=4)`

, and thus acts like a cross between the two.

## Type and Constructors

`FrankenTuples.FrankenTuple`

— Type.`FrankenTuple{T<:Tuple, names, NT<:Tuple}`

A `FrankenTuple`

contains a `Tuple`

of type `T`

and a `NamedTuple`

with names `names`

and types `NT`

. It acts like a cross between the two, like a partially-named tuple.

The named portion of a `FrankenTuple`

can be accessed using `NamedTuple`

, and the unnamed portion can be accessed with `Tuple`

.

**Examples**

```
julia> ft = FrankenTuple((1, 2), (a=1, b=2))
FrankenTuple((1, 2), (a = 1, b = 2))
julia> Tuple(ft)
(1, 2)
julia> NamedTuple(ft)
(a = 1, b = 2)
```

`FrankenTuples.ftuple`

— Function.`ftuple(args...; kwargs...)`

Construct a `FrankenTuple`

from the given positional and keyword arguments.

**Examples**

```
julia> ftuple(1, 2)
FrankenTuple((1, 2), NamedTuple())
julia> ftuple(1, 2, a=3, b=4)
FrankenTuple((1, 2), (a = 3, b = 4))
```

`FrankenTuples.@ftuple`

— Macro.```
@ftuple (x...; y...)
@ftuple (a, x=t, b, y=u)
```

Construct a `FrankenTuple`

from the given tuple expression, which can contain both positional and named elements. The tuple can be "sectioned" in the same manner as a function signature, with positional elements separated from the named elements by a semicolon, or positional and named elements can be intermixed, occurring in any order.

**Examples**

```
julia> @ftuple (1, 2; a=3, b=4)
FrankenTuple((1, 2), (a = 3, b = 4))
julia> @ftuple (1, a=3, 2, b=4)
FrankenTuple((1, 2), (a = 3, b = 4))
```

## API

`FrankenTuple`

s adhere as closely as makes sense to the API for `Tuple`

s and `NamedTuple`

s.

`Core.Tuple`

— Type.`Tuple(ft::FrankenTuple)`

Access the `Tuple`

part of a `FrankenTuple`

, i.e. the "plain," unnamed portion.

`Core.NamedTuple`

— Type.`NamedTuple(ft::FrankenTuple)`

Access the `NamedTuple`

part of a `FrankenTuple`

, i.e. the named portion.

`Base.length`

— Function.`length(ft::FrankenTuple)`

Compute the number of elements in `ft`

.

`Base.isempty`

— Function.`isempty(ft::FrankenTuple)`

Determine whether the given `FrankenTuple`

is empty, i.e. has at least 1 element.

`Base.iterate`

— Function.`iterate(ft::FrankenTuple[, state])`

Iterate over `ft`

. This yields the values of the unnamed section first, then the values of the named section.

**Examples**

```
julia> ft = @ftuple (1, a=3, 2, b=4)
FrankenTuple((1, 2), (a = 3, b = 4))
julia> collect(ft)
4-element Array{Int64,1}:
1
2
3
4
```

`Base.keys`

— Function.`keys(ft::FrankenTuple)`

Get the keys of the given `FrankenTuple`

, i.e. the set of valid indices into `ft`

. The unnamed section of `ft`

has 1-based integer keys and the named section is keyed by name, given as `Symbol`

s.

**Examples**

```
julia> keys(ftuple(1, 2; a=3, b=4))
(1, 2, :a, :b)
```

`Base.values`

— Function.`values(ft::FrankenTuple)`

Get the values of the given `FrankenTuple`

in iteration order. The values for the unnamed section appear before that of the named section.

**Examples**

```
julia> values(ftuple(1, 2; a=3, b=4))
(1, 2, 3, 4)
```

`Base.pairs`

— Function.`pairs(ft::FrankenTuple)`

Construct a `Pairs`

iterator that associates the `keys`

of `ft`

with its `values`

.

**Examples**

```
julia> collect(pairs(ftuple(1, 2; a=3, b=4)))
4-element Array{Pair{Any,Int64},1}:
1 => 1
2 => 2
:a => 3
:b => 4
```

`Base.getindex`

— Function.`getindex(ft::FrankenTuple, i)`

Retrieve the value of `ft`

at the given index `i`

. When `i::Integer`

, this gets the value at index `i`

in iteration order. When `i::Symbol`

, this gets the value from the named section with name `i`

. (`getproperty`

can also be used for the `Symbol`

case.)

**Examples**

```
julia> ftuple(1, 2; a=3, b=4)[3]
3
julia> ftuple(1, 2; a=3, b=4)[:a]
3
```

`Base.firstindex`

— Function.`firstindex(ft::FrankenTuple)`

Retrieve the first index of `ft`

, which is always 1.

`Base.lastindex`

— Function.`lastindex(ft::FrankenTuple)`

Retrieve the last index of `ft`

, which is equivalent to its `length`

.

`Base.first`

— Function.`first(ft::FrankenTuple)`

Get the first value in `ft`

in iteration order. `ft`

must be non-empty.

`Base.tail`

— Function.`Base.tail(ft::FrankenTuple)`

Return the tail portion of `ft`

: a new `FrankenTuple`

with the first element of `ft`

removed. `ft`

must be non-empty.

**Examples**

```
julia> Base.tail(ftuple(a=4, b=5))
FrankenTuple((), (b = 5,))
```

`Base.empty`

— Function.`empty(ft::FrankenTuple)`

Construct an empty `FrankenTuple`

.

`Base.eltype`

— Function.`eltype(ft::FrankenTuple)`

Determine the element type of `ft`

. This is the immedate supertype of the elements in `ft`

if they are not homogeneously typed.

**Examples**

```
julia> eltype(ftuple(1, 2; a=3, b=4))
Int64
julia> eltype(ftuple(0x0, 1))
Integer
julia> eltype(ftuple(a=2.0, b=0x1))
Real
julia> eltype(ftuple())
Union{}
```

## Additional Methods

These are some additional ways to use `FrankenTuple`

s. The most interesting of these is perhaps `hasmethod`

, which permits looking for methods that have particular keyword arguments. This is not currently possible with the generic method in Base.

`Base.hasmethod`

— Function.`hasmethod(f::Function, ft::Type{<:FrankenTuple})`

Determine whether the function `f`

has a method with positional argument types matching those in the unnamed portion of `ft`

and with keyword arguments named in accordance with those in the named portion of `ft`

.

Note that the types in the named portion of `ft`

do not factor into determining the existence of a matching method because keyword arguments to not participate in dispatch. Similarly, calling `hasmethod`

with a `FrankenTuple`

with an empty named portion will still return `true`

if the positional arguments match, even if `f`

only has methods that accept keyword arguments. This ensures agreement with the behavior of `hasmethod`

on `Tuple`

s.

More generally, the names in the `FrankenTuple`

must be a subset of the keyword argument names in the matching method, *except* when the method accepts a variable number of keyword arguments (e.g. `kwargs...`

). In that case, the names in the method must be a subset of the `FrankenTuple`

's names.

**Examples**

```
julia> f(x::Int; y=3, z=4) = x + y + z;
julia> hasmethod(f, FrankenTuple{Tuple{Int},(:y,)})
true
julia> hasmethod(f, FrankenTuple{Tuple{Int},(:a,)) # no keyword `a`
false
julia> g(; a, b, kwargs...) = +(a, b, kwargs...);
julia> hasmethod(g, FrankenTuple{Tuple{},(:a,:b,:c,:d)}) # g accepts arbitrarily many kwargs
true
```

`FrankenTuples.ftcall`

— Function.`ftcall(f::Function, ft::FrankenTuple)`

Call the function `f`

using the unnamed portion of `ft`

as its positional arguments and the named portion of `ft`

as its keyword arguments.

**Examples**

```
julia> ftcall(mapreduce, ftuple(abs2, -, 1:4; init=0))
-30
```