Created
March 3, 2026 15:40
-
-
Save ickc/937210e46b58fe9ba309a5d7c522c3a9 to your computer and use it in GitHub Desktop.
Revisions
-
ickc created this gist
Mar 3, 2026 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,81 @@ # -*- coding: utf-8 -*- # %% [markdown] # # NTuple invariance vs. tuple covariance # # Illustrates why `NTuple{N, <:AbstractFloat}` and `NTuple{N, AbstractFloat}` # mean very different things. # %% # --- helper --- check(label, val) = println(label, ": ", val) # %% [markdown] # ## 1. `NTuple{N, <:AbstractFloat}` — invariant TypeVar # # `NTuple{2, <:AbstractFloat}` expands to `NTuple{2, T} where T <: AbstractFloat`. # Julia must solve `T` to a **single** concrete type that satisfies every element, # so both elements must share exactly the same type. # %% # Both Float64 → T = Float64 ✓ check("(1.0, 2.0) <: NTuple{2,<:AbstractFloat}", (1.0, 2.0) isa NTuple{2, <:AbstractFloat}) # Both Float32 → T = Float32 ✓ check("(1f0, 2f0) <: NTuple{2,<:AbstractFloat}", (1.0f0, 2.0f0) isa NTuple{2, <:AbstractFloat}) # Float64 + Float32 → no single T satisfies both ✗ check("(1.0, 1f0) <: NTuple{2,<:AbstractFloat}", (1.0, 1.0f0) isa NTuple{2, <:AbstractFloat}) # %% [markdown] # ## 2. `NTuple{N, AbstractFloat}` — covariant tuple # # `NTuple{2, AbstractFloat}` means `Tuple{AbstractFloat, AbstractFloat}`. # Julia tuples are **covariant**: `Tuple{A,B} <: Tuple{P,Q}` holds whenever # `A <: P` and `B <: Q` are satisfied *independently* for each position. # `A` and `B` do **not** need to be the same type. # %% # Both Float64 → Float64 <: AbstractFloat ✓ check("(1.0, 2.0) <: NTuple{2,AbstractFloat}", (1.0, 2.0) isa NTuple{2, AbstractFloat}) # Float64 + Float32 → each position checked independently ✓ check("(1.0, 1f0) <: NTuple{2,AbstractFloat}", (1.0, 1.0f0) isa NTuple{2, AbstractFloat}) # Float64 + Int (not <: AbstractFloat) ✗ check("(1.0, 1) <: NTuple{2,AbstractFloat}", (1.0, 1) isa NTuple{2, AbstractFloat}) # %% [markdown] # ## 3. Type-level check (dispatch) # # The same distinction appears in method dispatch / `<:` on types. # %% # NTuple{2,<:AbstractFloat} requires a common T check("Tuple{Float64,Float64} <: NTuple{2,<:AbstractFloat}", Tuple{Float64, Float64} <: NTuple{2, <:AbstractFloat}) check("Tuple{Float64,Float32} <: NTuple{2,<:AbstractFloat}", Tuple{Float64, Float32} <: NTuple{2, <:AbstractFloat}) println() # NTuple{2,AbstractFloat} only needs each position to be <: AbstractFloat check("Tuple{Float64,Float64} <: NTuple{2,AbstractFloat}", Tuple{Float64, Float64} <: NTuple{2, AbstractFloat}) check("Tuple{Float64,Float32} <: NTuple{2,AbstractFloat}", Tuple{Float64, Float32} <: NTuple{2, AbstractFloat}) # %% [markdown] # ### Summary # # | Syntax | Constraint | # |--------|-----------| # | `NTuple{N, <:AbstractFloat}` | all N elements must be the **same** subtype T | # | `NTuple{N, AbstractFloat}` | each element only needs to satisfy `<: AbstractFloat` independently |