Feedback request: Should Mojo adopt "specialization" for what we currently call "parameters"?

I kinda like the idea of using meta. Short and familiar (e.g. meta programming). meta arguments and meta parameters would mean and refer to the same thing. It may even be able to replace comptime, e.g. meta if, meta for, although I think comptime is more descriptive. The only downside is… well, you know :slight_smile:

1 Like

@JosephWakeling @duck_tape @Nick @owenhilyard

I have no problem using parameter for the definition site and argument for the call site.

This doesn’t cover how we mark functions as eligible for compile-time evaluation. Idris has an appealing model where anything known to be total can be evaluated at compile time. That would be beautiful. Given the current timeframe, I am not sure Mojo can move that far in that direction. This leaves explicit marker for compile-time eligibility, similar to what @parameter does today:

  • @static doesn’t feel aligned with the language.

  • @comptime_eligible or @comptime_capable describe the behavior, but they are wordy. (Not much more than @fieldwise_init, though.)

Coming back to the original question: is there consensus that replacing overloaded or confusing terms with unambiguous ones is a net benefit for new adopters? If so, there isn’t much time for bikeshedding:

  • What should the compile-time process be called? I’m still a fan of compile-time specialization. (Elsewhere, compile-time binding was suggested, but I associate binding with symbol definition, not narrowing.)

  • What would the elements inside the square brackets be called? “Compile-time parameters” seems to be the emerging consensus, whether type or value.

  • What marker or intrinsic should indicate that a function or method can be used at compile time as well as run time?

  • What keyword replaces Parameters: in docstrings?

Thanks for your insights.

The screenshot below is what prompted the discussion.

1 Like

Depends on whether you care about separating them into two distinct groups or not. Assuming that runtime parameters are being documented as Arguments: (not unreasonable, as you’re telling the caller what they can use), how about Generic arguments: to replace Parameters?

Or, if you don’t care about documenting them separately, just document everything in the same Arguments: block.

1 Like

I still lean towards generics because I think that’s going to be the most familiar to the widest range of people. For people who want to dive a bit deeper, we can start exposing details of the compiler, such as the elaborator, generators, some of what the parser does, etc. I think trying to have another level in between “it’s a really fancy form of monomorphized generics” and what the compiler team uses internally is going to confuse people more. As far as I’m aware, things currently act a bit like type-level curried functions (ex: Scalar = SIMD[_, 1]) which almost mimics the monomorphization process. It feels like there’s a good explanation in here that can help bridge people from “fancy generics” to how things actually work without too much confusion.

+1 to “Compile-time parameters”

At present, I think that using comptime assert to indicate the inverse is a better idea. Many functions will run at compile time without the authors really thinking about it (for example, many of the fallback kernels in MAX will run at compile time if you REALLY want them to), so it would be better to make it opt out for things that will produce a nasty error message if run at compile time (such as IO functions). These asserts can be fairly low level, so most people who aren’t directly plumbing in FFI or a new syscall interface should never need to touch it.

I think trying to make keep the distinction of parameters as compile-time and arguments as run-time is a good idea.

I’m not sure there is a good term for this that isn’t overly academic. C++, Rust, Java and C# all use “parameter” for compile-time types and values, and those alone will cover a substantial chunk of developers worldwide who have some prior knowledge of generics in a static type system, and I don’t think there’s sufficient value in using a different term to make breaking that convention worthwhile.

2 Likes

I agree with everything Owen said, especially using comptime assert for opting out of comptime.

My only quibble would be to say comptime parameters instead of Compile-time parameters since the keyword is comptime.

I have no problem using parameter for the definition site and argument for the call site.

I think this is kind of addressed in Owen’s reply, but I’d argue for what we call these things to be the same in both places.

3 Likes

What about docstring ergonomics? Most languages don’t solve this cleanly so I don’t know of established precedent to borrow from. One constraint: whatever word is chosen must be distinct enough from Parameters: (things declared that are populated at runtime) that a reader immediately knows which section is which. Current candidates:

  • Statics: “static” as compile-time is broadly understood

  • Bindings: parameters that bind names to types and values at compile time

  • Specializations: covers both type and value compile time parameters

What do you like? What else belongs on this list?

My take:

fn foo[T: AnyType, N: Int](s: String) :
    # T: (comptime type) parameter
    # N: (comptime value) parameter
    # s: (runtime) argument
    pass

def main ():
    foo[Bool, 42]("hello")
    # Bool: (comptime type) parameter
    # 42: (comptime value) parameter
    # "hello": (runtime) argument

Motivation:

  • use the same name for the same things on declaration and on call side.
  • parameter is always used for comptime
  • argument is always used for runtime

This would still allow to just use param (parameter) and arg (argument) as easy to use abbreviations in discussions and it is implicitly clear which is comptime and which is runtime.

1 Like

I don‘t really understand this constraint. I feel comfortable with the current state of the docstrings using Parameters for comptime parameters and Args for runtime arguments.

example:

    def __contains__[
        U: Equatable & Copyable, //
    ](self: List[U, ...], value: U) -> Bool:
        """Verify if a given value is present in the list.

        Parameters:
            U: The type of the elements in the list. Must implement the
              trait `Equatable`.

        Args:
            value: The value to find.
2 Likes

My 2-cents: Comptimes: or Generics: could be used in place of Parameters: in docstrings.

Though I have the same feeling as @christoph_schlumpf . Maybe I’ve been using Mojo long enough to develop blind spots, but Parameters in my head translate to Comptime Parameters pretty easily.

3 Likes

But I see that by general definition parameter is used for declaration side variables and argument for call side values. So Mojo is not following this definition ATM.

If one would like to use "parameter“ for runtime and comptime declarations an easy solution would be to call them alike:

    def __contains__[
        U: Equatable & Copyable, //
    ](self: List[U, ...], value: U) -> Bool:
        """Verify if a given value is present in the list.

        Comptime Parameters:
            U: The type of the elements in the list. Must implement the
              trait `Equatable`.

        Parameters:
            value: The value to find."""

Maybe this is more „sound“ and consistent in the long run.

def foo[T: AnyType, N: Int](s: String):
    # T: comptime (type) parameter
    # N: comptime (value) parameter
    # s: (runtime) parameter
    pass

def main ():
    foo[Bool, 42]("hello")
    # Bool: comptime (type) argument
    # 42: comptime (value) argument
    # "hello": (runtime) argument

Sorry for circulating back to your original statement, @erica_sadun . I should have looked up the definitions first :wink:.

2 Likes

It seems this issue with “spec” can be resolved with bit of an out of the box thinking that immediately came to mind from my first language - spez (German for Specialization: Spezialisierung). Still sounds English enough. I don’t know.

I like what you proposed about compile-time specialization. I think generics wouldn’t quite capture it if your idea is indeed to permit specialization as indicated in your original post about compile-time refinement. Another “Pythonic” feeling language I use a lot is Nim. It resolves such ambiguities with compile time pragmas {.compileTime.} for opt-in compile-time specifics but but implicitly allows dual use of both compile-time and runtime definitions. A compile-time definition should not generate side effects that can only be known at run time. So, it has a func keyword which is essentially the same as proc with the {.noSideEffect.} pragma. But otherwise it makes the essential distinction of parameters (named declarations), and arguments (values being passed).

With all that said, my 2 cents for your 4 what questions would be:

  1. compile-time specialization sounds good.
  2. Compile-time parameters” also seems natural.
  3. Does it need a marker? Why not let it be implicit, and make only compile-time specialization explicit?
  4. I think one can qualify Parameters to be specific perhaps? Nim uses Generic Parameters for Type, and Static Parameters for Value - both would be compile-time parameters in this case.
1 Like

How about using just one “Parameters” docstring section and prepending the comptime parameters with comptime?

def foo[T: AnyType, n: Int](s: String):
    """Does some fancy stuff.
    
    Parameters:
        comptime T: The type to use.
        comptime n: The number to use.
        s: The string to work with.
    """
    pass

def main ():
    foo[Bool, 42]("hello")
    # Bool: comptime (type) argument
    # 42: comptime (value) argument
    # "hello": (runtime) argument

Pro: explicit and familiar (no new concept/naming needed).

Con: maybe too verbose?

3 Likes

That sounds like a good way to do it.

If verbosity is a concern, perhaps prefixing comptime to the relevant parameters might be treated as a style preference rather than something necessary?

2 Likes

Yeah, I was thinking too that maybe we can just have a “Parameters” section without requiring explicit comptime mentioning. In struct declarations for example all "Parameters“ are comptime so it would be redundant to mention it there.

Here how it might look like when not requiring to mention comptime in docstring:

struct MyType[T: AnyType]():
    """A simple type.

    Parameters:
        T: The type of the value to be stored.
    """
    var value: T


def foo[T: AnyType, n: Int](s: String):
    """Does some fancy stuff.
    
    Parameters:
        T: The type to use.
        n: The number to use (comptime).
        s: The string to work with.
    """
    pass

def main ():
    foo[Bool, 42]("hello")
    # Bool: comptime (type) argument
    # 42: comptime (value) argument
    # "hello": (runtime) argument

The docstring must not necessary reflect everything that is already clear from looking at the declaration but should give additional semantic information.

3 Likes