# \[ANN\] TypeUtils: dealing with types in Julia

**URL:** https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584
**Category:** Package Announcements
**Tags:** package, announcement
**Created:** [July 13, 2023, 5:09pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584 "2023-07-13T17:09:43Z")
**Posts on this page:** 18
**Page:** 1

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [July 13, 2023, 5:09pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/1 "2023-07-13T17:09:43Z")

</div>

New package [`TypeUtils`](https://github.com/emmt/TypeUtils.jl) provides useful methods to deal with types in Julia.

## Cast value to type

The method, `as` is designed to _cast_ a value to a given type. The name was inspired by the built-in [Zig](https://ziglang.org/) function [`@as`](https://ziglang.org/documentation/master/#as).

A first usage is:

```julia
as(T, x)

```

which yields `x` converted to type `T`. This behaves like a lazy version of `convert(T,x)::T` doing nothing if `x` is already of type `T` and performing the conversion and the type assertion otherwise.

By default, the `as` method calls `convert` only if needed but also implements a number of conversions not supported by `convert`. The `as` method is therefore a bit more versatile than `convert` while relaxing the bother to remember which function or constructor to call to efficiently perform the intended conversion. For example:

```julia
julia> as(Tuple, CartesianIndex(1,2,3)) # yields tuple of indices
(1, 2, 3)

julia> as(CartesianIndex, (1,2,3)) # calls constructor
CartesianIndex(1, 2, 3)

julia> as(Tuple, CartesianIndices(((-2:5), (1:3)))) # yields tuple of index ranges
(-2:5, 1:3)

julia> as(CartesianIndices, ((-2:5), (1:3))) # calls constructor
CartesianIndices((-2:5, 1:3))

julia> as(String, :hello) # converts symbol to string
"hello"

julia> as(Symbol, "hello") # converts string to symbol
:hello

```

Another usage is:

```julia
as(T)

```

which yields a callable object that converts its argument to type `T`. This can be useful with `map`. For instance:

```julia
map(as(Int), dims)

```

to convert `dims` to a tuple (or array) of `Int`s.

Additional conversions becomes possible if another package such as [`TwoDimensonal`](https://github.com/emmt/TwoDimensional.jl) is loaded.

## Parameter-less type

The call:

```julia
parameterless(T)

```

yields the type `T` without parameter specifications. For example:

```julia
julia> parameterless(Vector{Float32})
Array

```

## Deal with array element types

The `TypeUtils` package provides a few methods to deal with array element types:

- `promote_eltype(args...)` yields the promoted element type of the arguments `args...` which may be anything implementing the `eltype` method.

- `convert_eltype(T,A)` yields an array with the same entries as `A` except that their type is `T`.

- `as_eltype(T,A)` yields an array which lazily converts its entries to type `T`. This can be seen as a memory-less version of `convert_eltype(T,A)`. The method `as_eltype` is similar to the method `of_eltype` provided by the [`MappedArrays`](https://github.com/JuliaArrays/MappedArrays.jl/tree/master) package.

Methods `convert_eltype(T,A)` and `as_eltype(T,A)` just return `A` itself if its elements are of type `T`.

## Type of result returned by a function

The call:

```julia
g = as_return(T, f)

```

yields a callable object such that `g(args...; kwds...)` lazily converts the value returned by `f(args...; kwds...)` to the type `T`. Methods `return_type(g)` and `parent(g)` can be used to respectively retrieve the type `T` and the original function `f`. A similar kind of object be built with the composition operator:

```julia
g = as(T)∘f

```

The method `return_type` may also be used as:

```julia
T = return_type(f, argtypes...)

```

to infer the type `T` of the result returned by `f` when called with arguments of types `argtypes...`.

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [July 15, 2023, 10:34am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/2 "2023-07-15T10:34:54Z")

</div>

Seems generally interesting, but not sure what specific usecases many of these functions solve — compared to Base/existing ones.

For example, when would one prefer `as` instead of `convert` or constructor?  
Why would one use `parameterless()`?  
`promote_eltype(args)` doesn’t really read simpler than `promote_type(map(eltype, args)...)`, and more people would know what the latter means.

Also, some `as` conversions are weird:

> [@emmt](#):
>
> ```julia
> julia> as(Tuple, CartesianIndices(((-2:5), (1:3)))) # yields tuple of index ranges
> (-2:5, 1:3)
> 
> ```

What’s the motivation to make this different from `Tuple(CartesianIndices(...))`? This inconsistency can cause definite confusion.

`convert_eltype` isn’t as generic as `map`:

```julia
julia> convert_eltype(Float64, (1, 2, 3))
ERROR: MethodError: no method matching convert_eltype(::Type{Float64}, ::Tuple{Int64, Int64, Int64})

julia> map(Float64, (1, 2, 3))
(1.0, 2.0, 3.0)

```

nor as efficient as specialized functions:

```julia
julia> convert_eltype(Float64, 1:5)
5-element Vector{Float64}:

julia> float(1:5)
1.0:1.0:5.0

```

---

<div class="post-metadata">

### Author: ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)
#### Post date: [July 16, 2023, 11:47am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/3 "2023-07-16T11:47:27Z")

</div>

> [@emmt](#):
>
> doing nothing if `x` is already of type `T` and performing the conversion and the type assertion otherwise

Erm, `convert` already does this.

```julia-repl
julia> @code_typed convert(Int, 2)
CodeInfo(
1 ─ return x
) => Int64

```

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 25, 2023, 1:48pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/4 "2023-10-25T13:48:14Z")

</div>

The `parameterless` method is used by the `StructuredArrays` package [here](https://github.com/emmt/StructuredArrays.jl/blob/1d3add14d81cd008b117574b1089f16ad5526efe/src/StructuredArrays.jl#L345).

The `as(T,x)` method is an attempt to unify the syntax. Sometime in Julia you’ll have to type `convert(T,x)`, sometime you have to type `T(x)`, and there may be other possibilities. This is the case for the example you provided: `convert(Tuple,CartesianIndices(....))` does not work and `as(Tuple,CartesianIndices(....))` amounts to calling `Tuple(CartesianIndices(....))`. Using `as(T,x)` avoids you to remember how exactly to do the conversion and makes your intent clear when reading the code.

The idea is to update `TypeUtils` so that a consistent result is returned as expected by the caller of `as(T,x)` for different types `T`. This would be _type-piracy_ to extend `convert` in that way.

Applying `convert_eltype` to a range has been fixed as of version 0.3.1 by this [commit](https://github.com/emmt/TypeUtils.jl/commit/5b4e55ceaf64cb1b6d054b3599806499420b6be6). I have just submitted a new version (0.3.2) to deal with tuples. Thanks for your suggestion.

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 25, 2023, 1:52pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/5 "2023-10-25T13:52:18Z")

</div>

> [@tecosaur](#):
>
> Erm, `convert` already does this.
> 
> ```julia
> julia> @code_typed convert(Int, 2)
> CodeInfo(
> 1 ─ return x
> ) => Int64
> 
> ```

Yes but, as said in the previous reply, `convert(T,x)` is not always implemented for given `T` and `typeof(x)` although it would make sense to have it. `as(T,x)` fills this gap avoiding type-piracy.

---

<div class="post-metadata">

### Author: ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)
#### Post date: [October 25, 2023, 4:51pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/6 "2023-10-25T16:51:03Z")

</div>

> [@emmt](#):
>
> Yes but, as said in the previous reply, `convert(T,x)` is not always implemented for given `T` and `typeof(x)` although it would make sense to have it. `as(T,x)` fills this gap avoiding type-piracy.

Really?

```julia-repl
julia> struct Foo end

julia> convert(Foo, Foo())
Foo()

julia> @which convert(Foo, Foo())
convert(::Type{T}, x::T) where T
     @ Base Base.jl:84

```

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [October 25, 2023, 4:53pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/7 "2023-10-25T16:53:44Z")

</div>

> [@emmt](#):
>
> `as(Tuple,CartesianIndices(....))` amounts to calling `Tuple(CartesianIndices(....))`

Does it? The results are different, in a weird and potentially confusing way:

```julia
julia> as(Tuple, CartesianIndices(((-2:5), (1:3))))
(-2:5, 1:3)

julia> Tuple(CartesianIndices(((-2:5), (1:3))))
(CartesianIndex(-2, 1), CartesianIndex(-1, 1), CartesianIndex(0, 1), CartesianIndex(1, 1), CartesianIndex(2, 1), CartesianIndex(3, 1), CartesianIndex(4, 1), CartesianIndex(5, 1), CartesianIndex(-2, 2), CartesianIndex(-1, 2), CartesianIndex(0, 2), CartesianIndex(1, 2), CartesianIndex(2, 2), CartesianIndex(3, 2), CartesianIndex(4, 2), CartesianIndex(5, 2), CartesianIndex(-2, 3), CartesianIndex(-1, 3), CartesianIndex(0, 3), CartesianIndex(1, 3), CartesianIndex(2, 3), CartesianIndex(3, 3), CartesianIndex(4, 3), CartesianIndex(5, 3))

```

Generally, semantics of `convert` is pretty well-defined. For now, I don’t really understand the semantics of functions like `convert_eltype`. It can be actively dangerous to code correctness, allowing to “convert” units in arbitrary ways:

```julia
julia> convert_eltype(typeof(1.0u"s"), 1:3)
3-element Vector{Quantity{Float64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}}:
 1.0 s
 2.0 s
 3.0 s

```

This isn’t even consistent with `as` that correctly refuses to convert:

```julia
julia> as(typeof(1.0u"s"), 1)
ERROR: DimensionError: s and 1 are not dimensionally compatible.

```

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 25, 2023, 5:22pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/8 "2023-10-25T17:22:01Z")

</div>

Your example is a no-op, so what?

BTW what I meant was: “not always implemented in a useful way”.

---

<div class="post-metadata">

### Author: ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)
#### Post date: [October 25, 2023, 5:24pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/9 "2023-10-25T17:24:27Z")

</div>

> [@emmt](#):
>
> Your example is a no-op, so what?

It shows that `convert(T, ::T)` is always implemented.

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 25, 2023, 5:47pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/10 "2023-10-25T17:47:20Z")

</div>

> [@aplavin](#):
>
> Does it? The results are different, in a weird and potentially confusing way:
> 
> ```julia-auto
> julia> as(Tuple, CartesianIndices(((-2:5), (1:3))))
> (-2:5, 1:3)
> 
> julia> Tuple(CartesianIndices(((-2:5), (1:3))))
> (CartesianIndex(-2, 1), CartesianIndex(-1, 1), CartesianIndex(0, 1), CartesianIndex(1, 1), CartesianIndex(2, 1), CartesianIndex(3, 1), CartesianIndex(4, 1), CartesianIndex(5, 1), CartesianIndex(-2, 2), CartesianIndex(-1, 2), CartesianIndex(0, 2), CartesianIndex(1, 2), CartesianIndex(2, 2), CartesianIndex(3, 2), CartesianIndex(4, 2), CartesianIndex(5, 2), CartesianIndex(-2, 3), CartesianIndex(-1, 3), CartesianIndex(0, 3), CartesianIndex(1, 3), CartesianIndex(2, 3), CartesianIndex(3, 3), CartesianIndex(4, 3), CartesianIndex(5, 3))
> 
> ```

Sorry, I was mistaken `CartesianIndex` and `CartesianIndices`. For the former, `as(Tuple,x)` is the same as `Tuple(x)` while for the latter it yields `x.indices` hence your result. This choice is deliberate and is documented [here](https://github.com/emmt/TypeUtils.jl#cast-value-to-type). At the time I decided this, it seemed to make sense (at least to me) and was useful in a number of situations.

> [@aplavin](#):
>
> ```julia-auto
> julia> as(typeof(1.0u"s"), 1)
> ERROR: DimensionError: s and 1 are not dimensionally compatible.
> 
> ```

This is normal as `convert` is called here and the units are not compatible (seconds against unitless).

To add to the inconsistent behavior you pointed:

> [@aplavin](#):
>
> ```julia-auto
> julia> convert_eltype(typeof(1.0u"s"), 1:3)
> 3-element Vector{Quantity{Float64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}}:
> 1.0 s
> 2.0 s
> 3.0 s
> 
> ```

consider the following (very similar) example:

```julia-auto
julia> convert_eltype(typeof(1.0u"s"), collect(1:3))
ERROR: DimensionError: s and 1 are not dimensionally compatible.
...

```

so I completely agree with you this behavior is inconsistent and can be fixed by having `convert_eltype` directly call `as` which is not currently the case (it calls the type constructor).

Thank you for pointing this issue, I will fix it very soon unless you have other objections…

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 25, 2023, 6:13pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/11 "2023-10-25T18:13:12Z")

</div>

I have investigating a bit. The behiavior comes from the different methods (in base Julia) for `map(T,r)` when `r` is a range and depending on whether `T` is a `Real` (or an `AbstractFloat`) or something else like `typeof(1.0u"s")`. Following your example:

```julia
julia> map(Float32, 1:3)
1.0f0:1.0f0:3.0f0

julia> map(typeof(u"1.0s"), 1:3)
3-element Vector{Quantity{Float64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}}:
1.0 s
2.0 s
3.0 s

```

For the moment, I am a bit uncertain about what is the most appropriate behavior and how to implement it. Yet I still agree that the current behavior of `convert_eltype` is inconsistent and has to be fixed

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 26, 2023, 8:35am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/12 "2023-10-26T08:35:19Z")

</div>

Commit [Fix convert\_eltype(T,A) when A is a range · emmt/TypeUtils.jl@6d32d7e · GitHub](https://github.com/emmt/TypeUtils.jl/commit/6d32d7e6d87eedf1639893d2d361a9c84cd83779) is an attempt to fix `convert_eltype` for ranges. As said in the doc., The idea is that a range should yield a range.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [October 26, 2023, 9:02am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/13 "2023-10-26T09:02:29Z")

</div>

> [@emmt](#):
>
> yields the type `T` without parameter specifications

1. This is very related to the `constructorof` function from the [`ConstructionBase`](https://juliahub.com/ui/Packages/General/ConstructionBase) package.
2. Relevant section in Julia’s Manual: [Design Patterns with Parametric Methods](https://docs.julialang.org/en/v1/manual/methods/#Design-Patterns-with-Parametric-Methods).
3. IMO, the use of `parameterless` or similar ideas very likely points to design flaws in a piece of Julia code and possible misunderstandings of how Julia should be used. Although it may sometimes appear convenient. Related discussion, design, issues: [Missing functionality: converting coefficients · Issue #449 · JuliaMath/Polynomials.jl · GitHub](https://github.com/JuliaMath/Polynomials.jl/issues/449)

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 26, 2023, 10:43am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/14 "2023-10-26T10:43:48Z")

</div>

Than you for pointing this. Package `ConstructionBase` is very interresting. I agree that `parameterless` (or similar) is not to be widely used, I only have one example of such a need (and I put the code in `TypeUtils` to not forget this _trick_).

The implementation of `constructorof` is more elegant than that of `parameterless`:

```julia
@generated function constructorof(::Type{T}) where T
    getfield(parentmodule(T), nameof(T))
end
@inline parameterless(::Type{T}) where {T} = getfield(Base.typename(T), :wrapper)

```

After benchmarking, I found that the two methods are as fast. Can you explain the needs to make `constructorof` a generated function (I found that it does not make it faster).

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [October 26, 2023, 11:12am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/15 "2023-10-26T11:12:41Z")

</div>

> [@emmt](#):
>
> After benchmarking, I found that the two methods are as fast. Can you explain the needs to make `constructorof` a generated function (I found that it does not make it faster).

Don’t know, perhaps making it a generated function was required for good type inference in earlier Julia versions. In any case, the Polynomials package uses the same approach as in TypeUtils, instead of the ConstructionBase approach:

> <https://github.com/JuliaMath/Polynomials.jl/blob/c0160d4da2de5a7694aed83d0665ea715c294516/src/contrib.jl#L139-L147>

BTW, there’s this relevant open feature request on the Julia GitHub:

> <https://github.com/JuliaLang/julia/issues/35543>
>
> Is there a function to strip the type parameters from a type? For example:
> 
> \`\`…\`
> strip\_type\_parameters(Array{Int,3}) # should return Array
> \`\`\`
> 
> There is one way to do this but it is not documented (pointed out by @jakobnissen):
> 
> \`\`\`
> strip\_type\_parameters(T) = Base.typename(T).wrapper
> \`\`\`
> 
> Therefore in principle it can change in future versions.
> 
> Why not have a stable API for this?

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [October 27, 2023, 5:50am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/16 "2023-10-27T05:50:11Z")

</div>

Right, this is the same implementation as in `TypeUtils` but `constructorof` in `ConstructionBase` seems less likely to be broken by low-level changes in Julia. I will change `parameterless` in `TypeUtils` accordingly.

---

<div class="post-metadata">

### Author: ![putianyi888](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/putianyi888/32/32279_2.png) [@putianyi888](https://discourse.julialang.org/u/putianyi888)
#### Post date: [February 1, 2024, 8:06pm UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/17 "2024-02-01T20:06:47Z")

</div>

While I don’t agree with the idea of `as` since it functions almost identical to `convert` and occupies a relatively simple name which can easily cause ambiguity, other methods are very useful to me and I appreciate the work.

---

<div class="post-metadata">

### Author: ![emmt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/emmt/32/5192_2.png) [@emmt](https://discourse.julialang.org/u/emmt)
#### Post date: [February 5, 2024, 8:45am UTC](https://discourse.julialang.org/t/ann-typeutils-dealing-with-types-in-julia/101584/18 "2024-02-05T08:45:06Z")

</div>

You can type `using TypeUtils: x, y, z` with `x`, `y`, and `z` the methods you want to use.

The rationale for `as` is precisely to have a short name and, compared to `convert`, to allow for conversions that are not supported by `convert` whose main purpose is to convert field values to be stored in mutable structures by the `fieldset!` method.
