{"id":17113020,"url":"https://github.com/emmt/typeutils.jl","last_synced_at":"2026-04-02T16:03:40.942Z","repository":{"id":171907782,"uuid":"616515069","full_name":"emmt/TypeUtils.jl","owner":"emmt","description":"Methods for dealing with types in Julia","archived":false,"fork":false,"pushed_at":"2026-03-11T07:43:31.000Z","size":229,"stargazers_count":11,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-11T14:35:14.782Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emmt.png","metadata":{"files":{"readme":"README.md","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-03-20T14:36:40.000Z","updated_at":"2026-03-11T07:43:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"98c98c3e-4b8e-4a1d-9dad-05600e996331","html_url":"https://github.com/emmt/TypeUtils.jl","commit_stats":null,"previous_names":["emmt/typeutils.jl"],"tags_count":35,"template":false,"template_full_name":null,"purl":"pkg:github/emmt/TypeUtils.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FTypeUtils.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FTypeUtils.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FTypeUtils.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FTypeUtils.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emmt","download_url":"https://codeload.github.com/emmt/TypeUtils.jl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emmt%2FTypeUtils.jl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31309586,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T17:02:20.510Z","updated_at":"2026-04-02T16:03:40.935Z","avatar_url":"https://github.com/emmt.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dealing with types in Julia\n\n[![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](./LICENSE.md)\n[![Build Status](https://github.com/emmt/TypeUtils.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/emmt/TypeUtils.jl/actions/workflows/CI.yml?query=branch%3Amain)\n[![Build Status](https://ci.appveyor.com/api/projects/status/github/emmt/TypeUtils.jl?svg=true)](https://ci.appveyor.com/project/emmt/TypeUtils-jl)\n[![version](https://juliahub.com/docs/General/TypeUtils/stable/version.svg)](https://juliahub.com/ui/Packages/General/TypeUtils)\n[![Coverage](https://codecov.io/gh/emmt/TypeUtils.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/emmt/TypeUtils.jl)\n[![deps](https://juliahub.com/docs/General/TypeUtils/stable/deps.svg)](https://juliahub.com/ui/Packages/General/TypeUtils?t=2)\n[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)\n\nPackage `TypeUtils` provides useful methods to deal with types in\n[Julia](https://www.julialang.org) and facilitate coding with numbers whether they have\nunits or not. The package provides methods to strip units from numbers or numeric types,\nconvert the numeric type of quantities (preserving their units if any), determine\nappropriate numeric type to carry computations mixing numbers with different types and/or\nunits. These methods make it easy to write code that works consistently for numbers with any\nunits (including none). The intention is that the `TypeUtils` package automatically extends\nits exported methods when packages such as\n[`Unitful`](https://github.com/PainterQubits/Unitful.jl) are loaded.\n\n\n## Cast value to type\n\nThe method, `as` is designed to *cast* a value to a given type. The name was inspired by\nthe built-in [Zig](https://ziglang.org/) function\n[`@as`](https://ziglang.org/documentation/master/#as).\n\nA first usage is:\n\n``` julia\nas(T, x)\n```\n\nwhich yields `x` converted to type `T`. This behaves like `convert(T,x)::T` doing nothing\nif `x` is already of type `T` and performing the conversion and the type assertion\notherwise.\n\nBy default, the `as` method calls `convert` only if needed but also implements a number of\nconversions not supported by `convert`. The `as` method is therefore a bit more versatile\nthan `convert` while relaxing the bother to remember which function or constructor to call\nto efficiently perform the intended conversion. For example:\n\n``` julia\njulia\u003e as(Tuple, CartesianIndex(1,2,3)) # yields tuple of indices\n(1, 2, 3)\n\njulia\u003e as(CartesianIndex, (1,2,3)) # calls constructor\nCartesianIndex(1, 2, 3)\n\njulia\u003e as(Tuple, CartesianIndices(((-2:5), (1:3)))) # yields tuple of index ranges\n(-2:5, 1:3)\n\njulia\u003e as(CartesianIndices, ((-2:5), (1:3))) # calls constructor\nCartesianIndices((-2:5, 1:3))\n\njulia\u003e as(String, :hello) # converts symbol to string\n\"hello\"\n\njulia\u003e as(Symbol, \"hello\") # converts string to symbol\n:hello\n```\n\nAnother usage is:\n\n``` julia\nas(T)\n```\n\nwhich yields a callable object that converts its argument to type `T`. This can be useful\nwith `map`. For instance:\n\n``` julia\nmap(as(Int), dims)\n```\n\nto convert `dims` to a tuple (or array) of `Int`s.\n\nAdditional conversions becomes possible if another package such as\n[`TwoDimensonal`](https://github.com/emmt/TwoDimensional.jl) is loaded.\n\nFor more general lazy conversions, consider [`lazymap` in this package](#lazy-maps).\n\n\n## Lazy maps\n\nGiven an array or an iterator `A`, a *lazy map* `B` is built by one of:\n\n```julia\nB = lazymap([T::Type,] f, A)\nB = lazymap([T::Type,] f, A::AbstractArray, f_inv = inverse(f))\n```\n\nwhich yield a view `B` of the array or iterator `A` such that the `i`-th element of `B` is\ngiven by `Bᵢ = as(T, f(Aᵢ))` with `Aᵢ` the `i`-th element of `A` and using the [`as`\nmethod](#cast-value-to-type) to convert the result of `f(Aᵢ)` to type `T`.\n\nOptional argument `T` is to explicitly specify the element type of `B`; otherwise, it is\ninferred from `f` and from the element type of `A`. The lazy map `B` has type-stable element\ntype in the sense that its element have guaranteed type `T`, even though `T` may be\nabstract.\n\nIf `A` is an array, `f_inv` is the assumed inverse of `f` such that `B[i] = x` has the side\neffect of modifying `A` by `A[i] = f_inv(x)`. If unspecified, `f_inv` is inferred by the\n`inverse` method of the\n[`InverseFunctions`](https://github.com/JuliaMath/InverseFunctions.jl) package. If `f_inv =\nthrow`, a read-only lazy map array is returned even though `inverse(f)` is known. Similarly,\nif `f = throw`, a write-only lazy map object is returned (you probably want to specify\n`f_inv` in this case).\n\nAs a special case:\n\n```julia\nC = lazymap(T::Type, A)\n```\n\nbuilds an object `C` that lazily maps the **constructor** `T` to the elements of `A`. This\nis not exactly the same as:\n\n```julia\nB = lazymap(T::Type, identity, A)\n```\n\nwhich builds an object `B` that lazily **converts** the elements of `A` to type `T`. In\nother words, the `i`-th element of `C` is given by `Cᵢ = T(Aᵢ)::T`, while the `i`-th element\nof `B` is given by `Bᵢ = as(T, Aᵢ)`. In both cases, it is asserted that `Cᵢ` and `Bᵢ` are of\ntype `T`. The two are equivalent if `T` is a numeric type (a sub-type of `Number`).\n\n\n## Unitless value or type\n\n`unitless(x)` yields `x` without its units, if any. `x` can be a number or a numeric type.\nIn the latter case, `unitless` behaves like `bare_type` described below.\n\n\n## Bare, real, and floating-point numerical types\n\n`bare_type(x)` yields the bare numeric type of `x` (a numeric value or type). If this\nmethod is not extended for a specific type, the fallback implementation yields\n`typeof(one(x))`. With more than one argument, `bare_type(args...)` yields the type\nresulting from promoting the bare numeric types of `args...`. With no argument,\n`bare_type()` yields `TypeUtils.BareNumber` the union of bare numeric types that may be\nreturned by this method.\n\n`real_type(x)` yields the bare real type of `x` (a numeric value or type). If this method\nis not extended for a specific type, the fallback implementation yields\n`typeof(one(real(x))`. With more than one argument, `real_type(args...)` yields the type\nresulting from promoting the bare real types of `args...`. With no argument, `real_type()`\nyields `Real` the super-type of types that may be returned by this method.\n\nThe only difference between `bare_type` and `real_type` is how they treat complex numbers.\nThe former preserves the complex kind of its argument while the latter always returns a\nreal type. You may assume that `real_type(x) = real(bare_type(x))`. Conversely,\n`convert_bare_type(T,x)` yields a complex result if `T` is complex and a real result if\n`T` is real whatever `x`, while `convert_real_type(T,x)` yields a complex result if `x` is\ncomplex and a real result if `x` is real, only the real part of `T` matters for\n`convert_real_type(T,x)`. See examples below.\n\n`floating_point_type(args...)` yields a floating-point type appropriate to represent the\nbare real type of `args...`. With no argument, `floating_point_type()` yields\n`AbstractFloat` the super-type of types that may be returned by this method. You may\nconsider `floating_point_type(args...)` as an equivalent to to`float(real_type(args...))`.\nThe floating-point type can be seen as the numerical precision for computations involving\n`args...`.\n\n`convert_bare_type(T,x)` converts the bare numeric type of `x` to the bare numeric type of\n`T` while preserving the units of `x` if any. Argument `x` may be a number or a numeric\ntype, while argument `T` must be a numeric type. If `x` is one of `missing`, `nothing`,\n`undef`, or the type of one of these singletons, `x` is returned.\n\n`convert_real_type(T,x)` converts the bare real type of `x` to the bare real type of `T`\nwhile preserving the units of `x` if any. Argument `x` may be a number or a numeric type,\nwhile argument `T` must be a numeric type. If `x` is one of `missing`, `nothing`, `undef`,\nor the type of one of these singletons, `x` is returned.\n\n`convert_floating_point_type(T,x)` converts the bare real type of `x` to the suitable\nfloating-point type for type `T` while preserving the units of `x` if any. Argument `x`\nmay be a number or a numeric type, while argument `T` must be a numeric type. If `x` is\none of `missing`, `nothing`, `undef`, or the type of one of these singletons, `x` is\nreturned. You may consider `convert_floating_point_type(T,x)` as an equivalent to to\n`convert_real_type(float(real_type(T)),x)`.\n\n## Numerical precision\n\n`get_precision(x)` or `get_precision(typeof(x))` yield the numerical precision of `x`. The\nprecision is a dimensionless floating-point type. If `x` is a floating-point value, the\nprecision of `x` is its floating-point type; if `x` stores floating-point values, the\nprecision is their promoted floating-point type; otherwise, the precision is\n`AbstractFloat`.\n\nTo adapt the numerical precision of an object `x`, call:\n\n``` julia\nadapt_precision(T::Type{\u003c:AbstractFloat}, x)\n```\n\nwhich yields an object similar to `x` but with numerical precision specified by the\nfloating-point type `T`. If `x` has already the required precision or if setting its\nprecision is irrelevant or not implemented, `x` is returned unchanged. Setting the\nprecision shall not change the dimensions of dimensionful numbers. If `T` is\n`AbstractFloat`, the default floating-point type `Float64` is assumed.\n\nFor a number `x`, `adapt_precision(T, x)` behaves as `convert_real_type(T, x)` and\n`adapt_precision(T, typeof(x))` may be used to infer the corresponding type with precision\n`T`.\n\nExample:\n\n```julia\njulia\u003e adapt_precision(Float32, (1, 0x07, (\"hello\", 1.0, 3.0 - 2.0im, π)))\n(1.0f0, 7.0f0, (\"hello\", 1.0f0, 3.0f0 - 2.0f0im, 3.1415927f0))\n```\n\nBasically, `adapt_precision` supports numbers, arrays of numbers, matrix factorizations,\nand (named) tuples. It can be specialized for other object types defined in foreign\npackages by specializing:\n\n```julia\nTypeUtils.adapt_precision(::Type{T}, x::SomeType) where {T\u003c:TypeUtils.Precision} = ...\n```\n\nwhere `SomeType` is the object type and `TypeUtils.Precision` is a public but non-exported\nsymbol defined in `TypeUtils` as the union of the concrete floating-point types of Julia:\n\n```julia\nconst Precision = Union{Float16,Float32,Float64,BigFloat}\n```\n\nThe restriction `T\u003c:Precision` in the function signature is to make sure that this version\nof `adapt_precision` is only called with a concrete floating-point type `T`.\n\n\n## Parameter-less type\n\nThe call:\n\n``` julia\nparameterless(T)\n```\n\nyields the type `T` without parameter specifications. For example:\n\n```julia\njulia\u003e parameterless(Vector{Float32})\nArray\n```\n\n## Deal with array shape, size, and axes\n\nThe `TypeUtils` package provides the following types for array shape, size, and axes:\n\n- `ArrayAxis` is an alias to the possible canonical types representing a single array\n  index range, that is `AbstractUnitRange{eltype(Dims)}`.\n\n- `ArrayAxes{N}` is an alias to the possible canonical types representing `N`-dimensional\n  array axes, that is `NTuple{N,ArrayAxis}`. For any `N`-dimensional array `A`, `axes(A)\n  \u003c: ArrayAxes{N}` should hold.\n\n- `ArrayShape{N}` is an alias to the `N`-tuple of array dimensions and/or index ranges to\n  which `as_array_shape`, `as_array_size`, or `as_array_axes` are applicable (see below).\n\nNote that `Dims{N}` is the same as `NTuple{N,Int}` in Julia and represents the result of\n`size(A)` for the `N`-dimensional array `A`, so `eltype(Dims) = Int`.\n\nGiven a list `inds...` of array dimension lengths (integers) and/or index ranges\n(integer-valued unit ranges), the following methods from `TypeUtils` are applicable:\n\n- `as_array_shape(inds...)` yields canonical array axes (if `inds...` contains any index\n  range other than `Base.OneTo`) or canonical array size (otherwise). The former is an\n  instance of `ArrayAxes{N}`, the latter is an instance of `Dims{N}`.\n\n- `as_array_axes(inds...)` yields canonical array axes, that is a `N`-tuple of type\n  `ArrayAxes{N}`.\n\n- `as_array_size(inds...)` yields canonical array size, that is a `N`-tuple of type\n  `Dims{N}`.\n\nOf course, these methods also accept that their arguments be specified by a tuple of\narray dimension lengths and/or index ranges, that is `(inds...,)` instead of `inds...` in\nthe above example.\n\nTo deal with individual array dimension length and/or index range:\n\n- `as_array_axis` converts its argument to a single array axis of type `ArrayAxis`.\n\n- `as_array_dim` converts its argument to a single array dimension length of type\n  `eltype(Dims)`, that is an `Int`.\n\nThe method `new_array(T, inds...)` yields a new array with elements of type `T` and shape\ndefined by `inds...`. Following the semantic of `as_array_shape`, the returned array is an\n`OffsetArray{T}` if `inds...` contains any index range other than `Base.OneTo` and an\n`Array{T}` otherwise. In the former case, an exception is thrown if the package\n`OffsetArrays` has not been loaded.\n\n\n## Deal with array element types\n\nThe `TypeUtils` package provides a few methods to deal with array element types:\n\n* `promote_eltype(args...)` yields the promoted element type of the arguments `args...`\n  which may be anything implementing the `eltype` method.\n\n* `convert_eltype(T,A)` yields an object similar to `A` except that its elements have type\n  `T`.\n\n* `as_eltype(T,A)` yields an array which lazily converts its entries to type `T`. This can\n  be seen as a memory-less version of `convert_eltype(T,A)`. The method `as_eltype` is\n  similar to the method `of_eltype` provided by the\n  [`MappedArrays`](https://github.com/JuliaArrays/MappedArrays.jl/tree/master) package.\n\nMethods `convert_eltype(T,A)` and `as_eltype(T,A)` just return `A` itself if its elements\nare of type `T`.\n\n\n## Type of result returned by a function\n\nThe call:\n\n``` julia\ng = as_return(T, f)\n```\n\nyields a callable object such that `g(args...; kwds...)` lazily converts the value\nreturned by `f(args...; kwds...)` to the type `T`. Methods `return_type(g)` and\n`parent(g)` can be used to respectively retrieve the type `T` and the original function\n`f`. A similar kind of object be built with the composition operator:\n\n``` julia\ng = as(T)∘f\n```\n\nThe method `return_type` may also be used as:\n\n``` julia\nT = return_type(f, argtypes...)\n```\n\nto infer the type `T` of the result returned by `f` when called with arguments of types\n`argtypes...`.\n\n\n## Destructure an object as values and restructure values as an object\n\nIt is sometime useful to collect the values stored by a structured object into simple\ncollection of values (a tuple or a vector). The reverse operation is also needed. The\ncall:\n\n``` julia\nvals = destructure(obj)\n```\n\nyields a tuple, `vals`, of the values of the structured object `obj` and, conversely, the\ncall:\n\n``` julia\nobj = restructure(T, vals)\n```\n\nbuilds a structured object of type `T` given `vals`, a tuple or a vector of its values.\n\nFor an immutable concrete object `obj`, the following identity holds:\n\n``` julia\nrestructure(typeof(obj), destructure(obj)) === obj\n```\n\nIt is also possible to destructure an object into a given vector of values:\n\n``` julia\ndestructure!(vals, obj)\n```\n\nOptionally, in `restructure` and `destructure!` methods, keyword `offset` may be specified\nto not start with the first value in `vals`.\n\nMethod `struct_length` yields the minimal number of values needed to destructure an\nobject.\n\n\n## Compatibility with `Unitful`\n\nThe following examples illustrate the result of the methods provided by `TypeUtils`, first\nwith bare numbers and bare numeric types, then with quantities:\n\n```julia\njulia\u003e using TypeUtils\n\njulia\u003e map(unitless, (2.1, Float64, true, ComplexF32))\n(2.1, Float64, true, ComplexF32)\n\njulia\u003e map(bare_type, (1, 3.14f0, true, 1//3, π, 1.0 - 2.0im))\n(Int64, Float32, Bool, Rational{Int64}, Irrational{:π}, Complex{Float64})\n\njulia\u003e map(real_type, (1, 3.14f0, true, 1//3, π, 1.0 - 2.0im))\n(Int64, Float32, Bool, Rational{Int64}, Irrational{:π}, Float64)\n\njulia\u003e map(x -\u003e convert_bare_type(Float32, x), (2, 1 - 0im, 1//2, Bool, Complex{Float64}))\n(2.0f0, 1.0f0, 0.5f0, Float32, Float32)\n\njulia\u003e map(x -\u003e convert_real_type(Float32, x), (2, 1 - 0im, 1//2, Bool, Complex{Float64}))\n(2.0f0, 1.0f0 + 0.0f0im, 0.5f0, Float32, ComplexF32)\n\njulia\u003e using Unitful\n\njulia\u003e map(unitless, (u\"2.1GHz\", typeof(u\"2.1GHz\")))\n(2.1, Float64)\n\njulia\u003e map(bare_type, (u\"3.2km/s\", u\"5GHz\", typeof((0+1im)*u\"Hz\")))\n(Float64, Int64, Complex{Int64})\n\njulia\u003e map(real_type, (u\"3.2km/s\", u\"5GHz\", typeof((0+1im)*u\"Hz\")))\n(Float64, Int64, Int64)\n```\n\n\n## Rationale\n\nThe following example shows a first attempt to use `bare_type` to implement efficient\nin-place multiplication of an array (whose element may have units) by a real factor (which\nmust be dimensionless in this context):\n\n```julia\nfunction scale!(A::AbstractArray, α::Number)\n    alpha = convert_bare_type(eltype(A), α)\n    @inbounds @simd for i in eachindex(A)\n        A[i] *= alpha\n    end\n    return A\nend\n```\n\nAn improvement is to realize that when `α` is a real while the entries of `A` are\ncomplexes, it is more efficient to multiply the entries of `A` by a real-valued multiplier\nrather than by a complex one. Implementing this is as simple as replacing\n`convert_bare_type` by `convert_real_type` to only convert the bare real type of the\nmultiplier while preserving its complex/real kind:\n\n```julia\nfunction scale!(A::AbstractArray, α::Number)\n    alpha = convert_real_type(eltype(A), α)\n    @inbounds @simd for i in eachindex(A)\n        A[i] *= alpha\n    end\n    return A\nend\n```\n\nThis latter version consistently and efficiently deals with `α` being real while the\nentries of `A` are reals or complexes, and with `α` and the entries of `A` being\ncomplexes. If `α` is a complex and the entries of `A` are reals, the statement `A[i] *=\nalpha` will throw an `InexactConversion` if the imaginary part of `α` is not zero. This\ncheck is probably optimized out of the loop by Julia but, to handle this with guaranteed\nno loss of efficiency, the code can be written as:\n\n```julia\nfunction scale!(A::AbstractArray, α::Union{Real,Complex})\n    alpha = if α isa Complex \u0026\u0026 bare_type(eltype(A)) isa Real\n        convert(real_type(eltype(A)), α)\n    else\n        convert_real_type(eltype(A), α)\n    end\n    @inbounds @simd for i in eachindex(A)\n        A[i] *= alpha\n    end\n    return A\nend\n```\n\nThe restriction `α::Union{Real,Complex}` accounts for the fact that in-place\nmultiplication imposes a dimensionless multiplier. Since the test leading to the\nexpression used for `alpha` is based on the types of the arguments, the branch is\neliminated at compile time and the type of `alpha` is known by the compiler. The\n`InexactConversion` exception may then only be thrown by the call to `convert` in the\nfirst branch of the test.\n\nThis seemingly very specific case was in fact the key point to allow for packages such as\n[LazyAlgebra](https://github.com/emmt/LazyAlgebra.jl) or\n[LinearInterpolators](https://github.com/emmt/LinearInterpolators.jl) to work seamlessly\non arrays whose entries may have units. The `TypeUtils` (formerly `Unitless`) package was\ncreated to cover this need as transparently as possible.\n\n\n## Related things\n\nThere exist objects or packages with functionalities similar to those provided by\n`TypeUtils` but with differences that justify the existence of this package.\n\n* Compared to `Iterators.map(f, A)` which is always an iterator, the object returned by\n  `lazymap(f, A)` is an (abstract) array if `A` is an array, an iterator otherwise.\n\n* `mapview(f, A)` using [`FlexiMaps`](https://github.com/JuliaAPlavin/FlexiMaps.jl) is very\n  similar to `lazymap(f, A)` but, with `FlexiMaps`, there is no equivalent to `lazymap(T, f,\n  A)` to specify an element type for the mapped view and directly build a\n  `FlexiMaps.MappedArray` with a given element type yields an abstract array whose `eltype`\n  is not the type of the result returned by `getindex` while `lazymap` takes care of\n  converting this result correctly. In `FlexiMaps`, there is no shortcut to build a\n  read-only view if the inverse function is known.\n\n* `mappedarray(f, A)` and `mappedarray(f, inv_f, A)` using\n  [`MappedArrays`](https://github.com/JuliaArrays/MappedArrays.jl) are similar to\n  `lazymap(f, A)` and `lazymap(f, A, inv_f)` but:\n  - `f_inv` is not automatically inferred by `mappedarray` if not specified;\n  - `A` must be an array in `mappedarray`;\n  - the element type of a mapped array is inferred and cannot be specified as for a lazy map\n    which is type-stable with respect to its `eltype`.\n\n* `as_eltype(T, A)`, a shortcut for `lazymap(T, identity, A)`, is the analogue of\n  `of_eltype(T, A)` in [`MappedArrays`](https://github.com/JuliaArrays/MappedArrays.jl).\n\n* `TypeUtils.LazyMaps` does not implement lazily mapping multiple arrays, a possibility\n  offered by `MappedArrays`, but this may be emulated by combining `TypeUtils.LazyMaps` and\n  [`ZippedArrays`](https://github.com/emmt/ZippedArrays.jl).\n\n* `BroadcastArray(f, A)` and `BroadcastArray{T}(f, A)` using\n  [`LazyArrays`](https://github.com/JuliaArrays/LazyArrays.jl) is similar to `lazymap(f, A)`\n  and `lazymap(T, f, A)` but broadcast arrays are read-only and restricted to arrays while\n  lazy maps are writable if inverse function is known or specified and can be used over\n  other collections than arrays.\n\nAs shown by [benchmark tests](./test/benchmarks.jl) for `TypeUtils.LazyMaps` and these\ndifferent packages, evaluating `B[i]` for an object `B` lazily representing `f.(A)` is as\nfast as calling `f(A[i])`. Also any of these objects can be used with no allocations and,\nexcept `BroadcastArray`, no construction overheads compared to `f(A[i])`. A `BroadcastArray`\nusing [`LazyArrays`](https://github.com/JuliaArrays/LazyArrays.jl) is as fast as `mapreduce`\nfor reductions (like a `sum`) of the broadcast array which provides some speedup for large\narrays.\n\nDirect dependencies:\n\n* [`InverseFunctions`](https://github.com/JuliaMath/InverseFunctions.jl) is used to infer\n  inverse functions.\n\n\n## Installation\n\n`TypeUtils` can be installed as any other official Julia packages. For example:\n\n```julia\nusing Pkg\nPkg.add(\"TypeUtils\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Ftypeutils.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femmt%2Ftypeutils.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femmt%2Ftypeutils.jl/lists"}