0

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?

6
  • You need a KnownNat (Dim v) constraint; then you can use sameNat @2 Proxy (natSing @(Dim v)) and similarly for 3. You will also need to tell it what to do when Dim v is neither 2 nor 3. Commented Jun 24 at 14:27
  • (Actually, I guess natSing isn't needed, you can just go sameNat @2 @(Dim v) Proxy Proxy if you want.) Commented Jun 24 at 14:36
  • Your actual problem has nothing to do with casing on the length of the input. Forget the n=3 case for a second. bar promises to take an argument of (a list of) anything which is a V.Vector, and return the same. However, worksFor2 promises to produce only a concrete (list of) Vec2. What if the caller passes an argument of type [Frob Foo] (where they have implemented V.Vector Frob Foo as appropriate) - you promise that bar produces the same, but how can that be, if it only produces a Vec2. Commented Sep 3 at 15:24
  • @user2407038, as I mentioned, being used to C++, I'm used to think of values-stored-in-a-type (e.g. the N in a std::array<T, N>) as being available at compile time for any requirements one might want to express, such as N == 2 || N == 3, which would accept/reject the caller code accordingly if it passes a V.Vector with one of those two possible lengths or not. I suppose in my original question I shuld have commented on bar's signature adding "how can I tell that V.Vector to only accept Vec2 and Vec3?" Commented Sep 3 at 16:36
  • Indeed, if you were to use a type approximately equivalent to std::array<T, N>, you would have an easy time. However, the constraint (NOT type) V.Vector is 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 constraint Vector you should use the Vec type directly, so your signature would be (..) => [Vec n a] -> [Vec n a] ... Commented Sep 3 at 19:31

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.