Search code examples
listrecursionfunctional-programmingidris

Can't index into a list constructed by a recursive function


I have a function getN' that is supposed to construct a list of n elements recursively, however I think I'm doing something wrong because I can't index into it and the output looks unexpected:

getN' : (Double -> Double -> Double) -> Double -> Double -> Double -> Int -> List Double
getN' f dt t0 y0 0 = []
getN' f dt t0 y0 n = rk4' :: getN' f dt (t0+dt) rk4' (n-1) where
    rk4' = rk4 f dt t0 y0

The output of getN' f 0.1 0 1 100 is:

1.0050062486962987 :: getN' f 0.1 0.1 1.0050062486962987 99 : List Double

Which looks unfamiliar to me. Specifically the syntax head::tail is familiar, but Idris usually prints out the entire result in the REPL, which suggests something isn't right in this instance.

The output of index 0 $ getN' f 0.1 0 1 100 is:

(input):1:9:When checking argument ok to function Prelude.List.index:
        Can't find a value of type
                InBounds 0 (getN' f 0.1 0.0 1.0 100)

What am I doing/getting wrong?


Solution

  • getN' isn't total, so the REPL doesn't evaluate the recursive call (as it could loop forever). Set %default total or total getN' : … to get a warning from the compiler, or check in the REPL with :total getN'. Idris can't reason about Int, and in this particular case, getN' f 0.1 0 1 -1 wouldn't stop.

    If you replace Int with Nat it works (with 0 to Z, n to (S n) and n - 1 to n). From another answer: The compiler does only know that subtracting 1 from an Integer results to an Integer, but not which number exactly (unlike when doing it with a Nat). This is because arithmetic with Integer, Int, Double and the various Bits are defined with primary functions like prim__subBigInt.

    Why index fails: it needs a proof InBounds k xs, that the list has at least k elements. In Haskell, !! is weaker, and thus can crash at runtime: (getN' f 0.1 0 1 100) !! 101 would compile, but will throw an exception. This can't happen in Idris:

    >:t index
    Prelude.List.index : (n : Nat) -> (xs : List a) -> {auto ok : InBounds n xs} ->  a
    
    >:printdef InBounds
    data InBounds : Nat -> List a -> Type where
      InFirst : InBounds 0 (x :: xs)
      InLater : InBounds k xs -> InBounds (S k) (x :: xs)
    

    auto means, that the compiler tries to search for a proof ok : InBounds n xs. But, again, it doesn't evaluate getN' …, so it doesn't find (or even searches) for a proof. That's the error you are getting: Can't find a value of type InBounds …