copilot icon indicating copy to clipboard operation
copilot copied to clipboard

Allow appending and then dropping the same number of elements from Stream

Open strikef opened this issue 9 months ago • 4 comments

https://hackage.haskell.org/package/copilot-language-4.3/docs/Copilot-Language-Operators-Temporal.html#v:drop

The elements must be realizable at the present time to be able to drop elements. For most kinds of streams, you cannot drop elements without prepending an equal or greater number of elements to them first, as it could result in undefined samples.

Current specification states that one may drop the elements from the stream as long as you have appended at least the same number of elements. But the current implementation throws DropIndexOverflow when you try to drop as many elements as appended.

By modifying analyzeDrop we can avoid exception, but copilot would emit incorrect C code from Drop k (Append k s). This is because Append k s emits an array of length k, but Drop k s gets converted to index k access, which becomes incorrect when drop length and append length are the same. So I also modified the Reify module s.t. Drop k (Append k s) can be simplified to s. This way we can achieve both correct C codegen & reduced constructor chain.

This modification has been verified using copilot-verifier using several homegrown test cases.

strikef avatar Mar 14 '25 04:03 strikef

Tagging @fdedden and @RyanGlScott so that they take a look.

ivanperez-keera avatar Mar 26 '25 00:03 ivanperez-keera

Good catch! Here is a concrete test case that demonstrates the bug in action:

{-# LANGUAGE NoImplicitPrelude #-}
module Main (main) where

import Language.Copilot

spec :: Spec
spec = do
  let s :: Stream Bool
      s = drop 2 ([False, False] ++ true)
  trigger "example" s [arg s]

main :: IO ()
main = interpret 5 spec

Intuitively, I would expect drop 2 ([False, False] ++ true) to be equivalent to true, but in practice, Copilot rejects this with an error:

$ runghc Bug.hs
Bug.hs: Copilot error: Drop index overflow!
CallStack (from HasCallStack):
  error, called at src/Copilot/Language/Error.hs:24:16 in copilot-language-4.3-inplace:Copilot.Language.Error

On the other hand, drop 1 ([False, False] ++ true) does not throw an error. As such, I think your suggested fix of using k > length xs is apt.

(It would be helpful to include something like the program above as a test case.)

RyanGlScott avatar Mar 26 '25 11:03 RyanGlScott

Good catch! Here is a concrete test case that demonstrates the bug in action:

{-# LANGUAGE NoImplicitPrelude #-}
module Main (main) where

import Language.Copilot

spec :: Spec
spec = do
  let s :: Stream Bool
      s = drop 2 ([False, False] ++ true)
  trigger "example" s [arg s]

main :: IO ()
main = interpret 5 spec

Intuitively, I would expect drop 2 ([False, False] ++ true) to be equivalent to true, but in practice, Copilot rejects this with an error:

$ runghc Bug.hs
Bug.hs: Copilot error: Drop index overflow!
CallStack (from HasCallStack):
  error, called at src/Copilot/Language/Error.hs:24:16 in copilot-language-4.3-inplace:Copilot.Language.Error

On the other hand, drop 1 ([False, False] ++ true) does not throw an error. As such, I think your suggested fix of using k > length xs is apt.

(It would be helpful to include something like the program above as a test case.)

Thanks for the review! Could you recommend a module to add such tests? I'm not very familiar with the Copilot source tree yet.

strikef avatar Mar 27 '25 05:03 strikef

Could you recommend a module to add such tests?

Since the property of interest is checked in copilot-language, I think it makes sense to add a regression test in the copilot-language test suite. Currently, the copilot-language test suite only contains property-based tests (via QuickCheck), however. We could imagine adding some additional machinery to support a unit test that checks to see if reifying drop 2 [False, False] ++ true throws an exception or not, similar to this unit test in copilot-theorem.

This would take a fair bit of extra work, however, so I'll defer to @ivanperez-keera on whether we want to do all of this as part of this PR.

RyanGlScott avatar Mar 27 '25 13:03 RyanGlScott