Well… I’m not sure what exactly you mean by that. In considering the possibilities, you may be saying that from a place of understanding things fine now and I should be saying “Yes, exactly”, or you may be saying that from a place of having some confusions still and I should be saying “No, not quite”. So here’s what I’ll say in response (and let me know if it’s helpful or not to the point):
A corecursive function doesn’t take a Stream as input and produce something else as output. It takes something else as input and produces Streams as output. You’re aware of that, right?
So here’s an example of a corecursively defined function: consider the process of taking in a real number r in [0, 1) and then outputting either “Yes, r is zero” or “No, r is not zero; here’s the integer floor(10 * r) and here’s the new real number r - floor(10 * r)/10”. [On some implementation of real numbers for which you can robustly test whether they’re 0 or not and take integer floors and so on]
The corresponding corecursive function sends a real number to a Stream of integers, whose elements are the digits of the input’s decimal expansion, terminating after the last non-zero digit if it is possible to use such a representation.
It doesn’t start by asking of its input “Is this Empty or is this a Concatenation”, because its input isn’t a Stream. But it does start by asking of its input “Will this lead to an Empty output or will this lead to a Concatenation output?”. If you’re happy still thinking of that sort of thing as recursion, then you don’t need to think of recursion and corecursion as separate things.
Indeed, recursion and corecursion can be made to look very, very similar in Haskell (but this is basically because (typical) Haskell is infused with a corecursive spirit; it doesn’t actually have familiar inductive types like “finite natural numbers” and “finite lists” and so on; if you try to define “FinList” in Haskell, it’ll act exactly like “Stream”, with infinite lists and everything)
Consider this function definition:
curse :: Functor f => (a -> f a) -> (f b -> b) -> a -> b
curse unwrap wrap = let f x = wrap $ fmap f $ unwrap x in f
If you say how to “unwrap” inputs and how to correspondingly “wrap” outputs, this peels inputs apart and then mimicks their structure in the output, in a sense.
Recursion is what you get when you fix the type a to be an initial algebra, and unwrap to be the corresponding unwrapper. [For example, taking unwrap to be the function
unwrap :: [Int] -> Either () (Int, [Int])
unwrap [] = Left ()
unwrap (head:tail) = Right (head, tail)
, thinking of [Int] as FinList (and fmap as
fmap :: (a -> b) -> Either () a -> Either () b
fmap f (Left ()) = Left ()
fmap f (Right x) = Right (f x)
)]. Then the further “wrap” argument would be a specification of how to inductively build up an output value (with a base case, a recursive case, etc.); you would call this defining the function by recursion on the input FinList.
Corecursion is what you get when you fix “b” to be a terminal coalgebra, and “wrap” to be the corresopnding wrapper. [For example, taking wrap to be the function
wrap :: Either () (Int, [Int]) -> [Int]
wrap (Left ()) = Empty
wrap (Right (head, tail)) = Concatenation head tail
, thinking of [Int] as Stream]. Then the further “unwrap” argument would be a specification of how to peel apart the input (possibly producing a base case, possibly producing a non-base case…); you would call this defining the function by corecursion on the output Stream.
In some sense, it’s all the same if you can’t enforce finiteness requirements. If you can enforce finiteness requirements, then curse can’t always be well-defined; it must “break” in some way when asked to output the FinList containing the digits of 1/3, or the natural number containing the length of the Stream [0, 0, 0, 0, …]. And then you might want to learn what an initial algebra is and a terminal coalgebra and so on, to understand what kinds of things curse is actually well-defined on. But you just want to understand what curse does as a down-in-the-trenches, spare-me-the-theory programmer, then you can just read its code above, and not worry about when it should be called recursion, corecursion, or even something else.