why do we have to add t in our @function even though t is not used
Imagine the opposite: imagine a system in which ode45 somehow knew to omit passing in t when t is not used in the function. What would that look like?
Well, for one thing in such a case you would have @() with a single parameter, like @(x) x.*(4-x) . So when ode45 received the function, it would hypothetically look and see that the function only accepted a single parameter. But how would ode45 know whether it was the time variable or the boundary conditions that were being omitted? Would you expect ode45() to look at the name of the single parameter to figure out which of the two parameters it was? Perhaps you would expect that... but if you look at the ode45() documentation, the representative variable names it uses in the documentation are t and y not t and x . So it might look at the @(x) and see a single parameter, and might compare the x it sees to t and decide they are not the same and form the hypothesis that t was omitted. But then it would have to look at the x and compare it to the expected y and determine that they are not the same, so it would have to form the hypothesis that neither t nor y were passed, and that instead what was being passed was an extra parameter (extra parameters can happen in some cases.)
That suggests that relying on parameter names to determine whether a parameter was omitted is not a good idea.
Suppose further that instead of @(x)x.*(4-x) what you had passed in was @MyFunction -- a handle to a real function instead of a handle to an anonymous function. Would you expect that ode45 would use some way to look at the dummy parameter names built into the real function? If so then what would happen in the case where the function was compiled into a .mex* file and so there are no names for the parameters?
ode45*() and all other MATLAB functions skill all that guessing about the intended meaning, by working with a strict positional notation. ode45() is not going to fish through the headers to figure out which parameters it needs to pass based on name: ode45() is just always going to pass the given function two parameters based just on position. The receiving function is not required to use what is passed in.
For the initial y0 conditions, what does -2:4:6 mean?
Same as if you saw that in some other context: it forms a numeric row vector starting with -2 and adding 4 each time until the addition would exceed 6. So -2, then (-2+4) --> +2, then (-2+4+4) --> +6 then it looks ahead to (-2+4+4+4) ---> 10 and sees that exceeds the upper bound 6 so it stops before putting 10 on the list. The final list is [-2, +2, +6] so a vector of three boundary conditions will be passed to the ode function.