This is a bit of a follow up to a previous question.
Here's two functions that take 2 or 3 inputs respectively, and produce each a list of fixed-size vectors, of sizes 2 and 3 respectively:
import qualified Data.Vector.Fixed as V
import qualified Data.Vector.Fixed.Boxed as B
worksFor2 :: Eq a => [a] -> [a] -> [B.Vec2 a]
worksFor2 = undefined
worksFor3 :: Eq a => [a] -> [a] -> [a] -> [B.Vec3 a]
worksFor3 = undefined
they are entirely different, from one other.
The output though, can be treated uniformly, so I have a function like this:
bar :: (Eq a, V.Vector v a, V.Vector v [a], V.Vector v [a], V.Vector v a)
=> [v a] -> [v a]
bar arr = undefined
(All those constraint are there because apparently I need them in the implementation of bar.)
On its own, this type-checks:
arr2 = bar (worksFor2 (undefined :: String) (undefined :: String))
arr3 = bar (worksFor3 (undefined :: String) (undefined :: String) (undefined :: String))
so it appears to me that arguments of types [B.Vec2 (Maybe a)] and [B.Vec3 (Maybe a)] can be passed through a [v (Maybe String)] parameter; my interpretation, is that the generic type [v (Maybe String)] is being instantiated with v == B.Vec2 and v == B.Vec3.
On the other hand, my intention is that bar itself should inspect the fixed-length of its input and call again either worksFor2 or worksFor3, something along these lines:
bar :: (Eq a, V.Vector v a, V.Vector v [a], V.Vector v [a], V.Vector v a)
=> [v a] -> [v a]
bar arr = let ns = V.length (head arr)
in if ns == 2
then worksFor2 (index' @0 arr) (index' @1 arr)
else worksFor3 (index' @0 arr) (index' @1 arr) (index' @2 arr)
I would expect this to be possible, for the simple fact that V.length (head arr) is actually baked into the type of arr, but it doesn't work:
• Could not deduce ‘v ~ B.Vec 2’
from the context: (Eq a, V.Vector v [a], V.Vector v a)
bound by the type signature for:
bar :: forall a (v :: * -> *).
(Eq a, V.Vector v a, V.Vector v [a], V.Vector v [a],
V.Vector v a) =>
[v a] -> [v a]
at /path/to/Main.hs:(321,1)-(322,21)
Expected: [v a]
Actual: [B.Vec2 a]
‘v’ is a rigid type variable bound by
the type signature for:
bar :: forall a (v :: * -> *).
(Eq a, V.Vector v a, V.Vector v [a], V.Vector v [a],
V.Vector v a) =>
[v a] -> [v a]
at /path/to/Main.hs:(321,1)-(322,21)
• In the expression: worksFor2 (index' @0 arr) (index' @1 arr)
In the expression:
if ns == 2 then
worksFor2 (index' @0 arr) (index' @1 arr)
else
worksFor3 (index' @0 arr) (index' @1 arr) (index' @2 arr)
In the expression:
let ns = V.length (head arr)
in
if ns == 2 then
worksFor2 (index' @0 arr) (index' @1 arr)
else
worksFor3 (index' @0 arr) (index' @1 arr) (index' @2 arr)
• Relevant bindings include
arr :: [v a]
(bound at /path/to/Main.hs:323:5)
bar :: [v a] -> [v a]
(bound at /path/to/Main.hs:323:1) [GHC-25897]
I suppose my knowledge of C++ (if constexpr, and the whole template meta-programming) is creating this expectation, but still, I think the type system knows enough, in theory to do what I expect.
Is there a way to achieve what I want? Should I finally approach Template Haskell?
KnownNat (Dim v)constraint; then you can usesameNat @2 Proxy (natSing @(Dim v))and similarly for3. You will also need to tell it what to do whenDim vis neither 2 nor 3.natSingisn't needed, you can just gosameNat @2 @(Dim v) Proxy Proxyif you want.)barpromises to take an argument of (a list of) anything which is aV.Vector, and return the same. However,worksFor2promises to produce only a concrete (list of)Vec2. What if the caller passes an argument of type[Frob Foo](where they have implementedV.Vector Frob Fooas appropriate) - you promise thatbarproduces the same, but how can that be, if it only produces aVec2.Nin astd::array<T, N>) as being available at compile time for any requirements one might want to express, such asN == 2 || N == 3, which would accept/reject the caller code accordingly if it passes aV.Vectorwith one of those two possible lengths or not. I suppose in my original question I shuld have commented onbar's signature adding "how can I tell thatV.Vectorto only acceptVec2andVec3?"std::array<T, N>, you would have an easy time. However, the constraint (NOT type)V.Vectoris more like a concept (or sfinae pre C++20) in C++, in that if you constrain a function with a concept, the set of types it can accept is open. Instead of using the constraintVectoryou should use theVectype directly, so your signature would be(..) => [Vec n a] -> [Vec n a]...