0

I'm trying to reload a source file containing the following Idris 2 function on the REPL:

||| Apply a function across all elements of a vector.
||| @function The function to apply.
||| @input_vector The vector whose elements will be given as an argument to the function.
my_vect_map : (function : a -> b) -> (input_vector : Vect n a) -> Vect n b
my_vect_map function Nil = Nil
my_vect_map function (head :: tail) =
  (my_vect_map' ((function head) :: Nil) tail) where
    my_vect_map' : (accumulator : Vect length_b b) -> Vect length_a a -> Vect (length_b + length_a) b
    my_vect_map' accumulator Nil = accumulator
    my_vect_map' accumulator (head' :: tail') =
      my_vect_map' (accumulator ++ ((function head') :: Nil)) tail'

But it fails to type-check with the error:

Error: While processing right hand side of my_vect_map. While processing right hand side
of my_vect_map,my_vect_map'. Can't solve constraint
between: length_b (implicitly bound at page_75_section_3_2_exercises_solutions.idr:89:5--89:47) and plus length_b 0.

page_75_section_3_2_exercises_solutions.idr:89:36--89:47
    |
 89 |     my_vect_map' accumulator Nil = accumulator
    |                                    ^^^^^^^^^^^

Error(s) building file page_75_section_3_2_exercises_solutions.idr

Why can't the type-checker solve the constraint between length_b and plus length_b 0? What am I doing wrong and how might I correct it? I tried working through some examples by hand and it seems to work out:

my_vect_map id [] => Nil => []

my_vect_map id ['a'] => my_vect_map id ('a' :: Nil) => my_vect_map' ((id 'a') :: Nil) Nil => my_vect_map' ('a' :: Nil) Nil => ('a' :: Nil) => ['a']
                                                                                                          ^length_b=1  ^length_a=0            ^length=1=length_b+length_a

my_vect_map id ['a', 'b'] => my_vect_map id ('a' :: ('b' :: Nil)) => my_vect_map' ((id 'a') :: Nil) ('b' :: Nil) => my_vect_map' ('a' :: Nil) ('b' :: Nil) => my_vect_map' (('a' :: Nil) ++ ((id 'b') :: Nil)) Nil => my_vect_map' (('a' :: Nil) ++ ('b' :: Nil)) Nil => my_vect_map' ('a' :: ('b' :: Nil)) Nil => ('a' :: ('b' :: Nil)) => ['a', 'b']
                                                                                                                                 ^length_b=1  ^length_a=1                                                                                                                             ^length_b=2           ^length_a=0                     ^length=2=length_b+length_a

Also, how might I make the type-checker realise that length_b + length_a is equal to n (because I don't think I managed to encode that relationship into the function)?

nicoty
  • 166
  • 11

1 Answers1

1

You can prove that n + 0 = n by using the rewrite rule plusZeroRightNeutral in Data.Nat.

You might want to rethink this function a bit though.

You can create a vector map pretty trivially:

my_vect_map : (a -> b) -> Vect n a -> Vect n b
my_vect_map fn [] = []
my_vect_map fn (x :: xs) = fn x :: my_vect_map fn xs

edit

Here's a tail recursive version of map:

mutual
  rhs : {m : Nat} -> (a -> b) -> a -> Vect m b -> Vect len a -> Vect (plus m (S len)) b
  rhs f x acc xs = rewrite sym $ plusSuccRightSucc m len in my_vect_map' f (f x :: acc) xs

  my_vect_map' : {m : Nat} -> (a -> b) -> Vect m b -> Vect n a -> Vect (m + n) b
  my_vect_map' f acc [] = rewrite plusZeroRightNeutral m in acc
  my_vect_map' f acc (x :: xs) = rhs f x acc xs

my_vect_map : (a -> b) -> Vect n a -> Vect n b
my_vect_map f = reverse . my_vect_map' f []

The only purpose of rhs is to expose len, the size of xs.

We've also used {} to bring type variables into scope on the value level.

Hope this helps.

414owen
  • 791
  • 3
  • 13
  • Thanks for letting me know about `plusZeroRightNeutral` and rewrite rules. Fwiw, I know that vector map can be trivially implemented using normal recursion, but I was trying to implement it with tail recursion instead. – nicoty Jan 18 '21 at 14:00
  • Ah I appreciate that you wrote a tail recursive implementation, it's a useful example to learn from! It's interesting that you lifted part of your function into the top level instead of using a closure like I did (does Idris not allow mutually recursive closures?) and that your function seems to prepend the elements to create a reversed vector which you then reverse to the correct order at the end, instead of appending the elements (is prepending + reversing maybe cheaper than appending?). – nicoty Jan 18 '21 at 15:26
  • 2
    You should be able to use a `where` block, you might have to write the signatures before the function bodies though (you'd have to check). Idris vectors are linked lists, so appending is `O(n)`. The `map` version you were trying to write would have ended up being `O(n^2)`, the reversed version is `O(n)`. – 414owen Jan 18 '21 at 15:38