savi-lang / savi

A fast language for programmers who are passionate about their craft.
BSD 3-Clause "New" or "Revised" License
156 stars 12 forks source link

Add basic generic functions #169

Open jemc opened 2 years ago

jemc commented 2 years ago

Currently Savi has no generic functions (functions with type parameters). We only have generic types (like Array and Map).

We want to add them, in at least some basic useful form, even if we don't satisfy every possible use case at the start.

jemc commented 2 years ago

The most immediate use case to satisfy is the common map functional operation, acting on a collection of items and emitting a new collection containing items made by transforming the input items in some arbitrary way. In Savi, this might end up being defined something like this, borrowing a bit from the forall keyword used for this purpose in Crystal:

:class ref Array(A)
  // ...

  :fun map Array(B)
    :for_all B
    :yields for B
    new_array = Array(B).new(@size)
    @each -> (item | new_array << yield item)
    new_array

Which should be able to be invoked like this, with the compiler using the yield block's result type to infer the type for B as USize, making the overall call return an Array(USize):

    names = ["Alice", "Bob", "Cyril"]
    sizes = names.map -> (name | name.size)

Note that this approach doesn't introduce a special syntax for explicitly specifying type parameters on the caller's side.

I think we can avoid introducing new syntax for this if we take an approach similar to Swift, wherein the type parameters must be trivially inferrable from being used somewhere else in the signature.

The example above had the B type trivially inferrable from the yield block result type (as noted by the explicit :yields for B declaration).

In Swift, if there is no clear way to infer via some other part of the function signature, the common workaround is to add a new function parameter that accepts the type to use as a value. This will probably adapt well to Savi, where we can use the non cap to pass a type as a value without needing to be holding an instantiation of it.

As an example of how that might look, here's a function that just returns a new array of an arbitrary type:

  :fun make_array(a A'non) Array(A)
    :for_all A
    Array(A).new

The tricky thing about this case in today's Savi language, is that we won't necessarily know what cap to use for the A (because we have nothing to infer the cap from, as the type parameter is only inferrable through the a A'non) parameter, which obscures the "true" cap of A with the non override.

However, I suspect this difficulty will be alleviated in the new capabilities system, so I don't want to worry too much about this case yet.

jemc commented 2 years ago

Accordingly, I'm marking this as blocked by the new capabilities system, though perhaps if pressed we could split the ticket in half to create one blocked ticket and one unblocked ticket, leaving the blocked case for a later effort.