modularml / mojo

The Mojo Programming Language
https://docs.modular.com/mojo/manual/
Other
22.45k stars 2.56k forks source link

[Feature Request] Where statement such as rust programming language #1431

Open mojo-for-ai opened 7 months ago

mojo-for-ai commented 7 months ago

Review Mojo's priorities

What is your request?

Since Mojo 🔥 supports the trait, supporting the where statement is very important.

trait SomeTrait1: fn required_method(self, x: Int): ...

trait SomeTrait2: fn required_method(self, x: Int): ...

fn fun_with_traits[T: AnyType](x: T) where T: SomeTrait1, SomeTrait2: x.required_method(42)

What is your motivation for this change?

Making Mojo 🔥 easier to understand and read, in my humble opinion

Any other details?

No response

YichengDWu commented 7 months ago

I think it better to simplify [T: AnyType] to [T].

rarebreed commented 7 months ago
fn fun_with_traits[T, R](x: T)
    where T: Trait1 + Trait2,
          R: Trait3 + Trait4: -> R
    return x.required_method(42)

But that might look alittle too rust-like. Mojo needs to appeal to pythonistas more than rustaceans. So ideally, as much of PEP-695 should be used as possible. Unfortunately, python doesn't have an intersection type yet like typesecript, but hopefully & is logically consistent with the union | operator

fn fun_with_traits[
    T: Trait1 & Trait2, 
    R: Trait3 & Trait4
](x: T): -> R
    return x.required_method(42)
YichengDWu commented 7 months ago

I want to upvote this request here. At first glance, I didn't realize the importance of "where", I thought it was just a syntax. Now I realize that when calling mojo and functions with parameters, you must use "all parameters". With the addition of the where statement, as @rarebreed pointed out, you can perform some type of operation within the where statement, thereby reducing the complexity of the syntax when calling.

I demonstrate this with another example here:

@value
struct StaticInt[T: Intable, V: T]:
    pass

fn static[T:Intable, V: T]() -> StaticInt[T, V]:
    return StaticInt[T, V]()

Note that when calling a static function, you must specify two parameters.

let a = static[Int, 3]()
(StaticInt[Int, 3]) a = {}

This is redundant since the type of 3 is known at compile time. One can simplify it using where


fn static[V: T]()  where [T: Intable] -> StaticInt[T, V]

Now I can create StaticInt[Int, 3] using static[3](). In other words, where defines a "computing space" for a parameter, reducing the "calling space".

It is worth mentioning that achieving this in Julia is effortless:

julia> struct S{T, V} end

julia> function static(x:: T) where {T}
              return S{T, x}
         end
static (generic function with 1 method)

julia> static(3)
S{Int64, 3}
YichengDWu commented 7 months ago

I think there is an easy work around in @rarebreed's example:


trait Trait1and2(Trait1, Trait2):
    ...

trait Trait3and4(Trait3, Trait4):
    ....

fn fun_with_traits[
    T: Trait1and2, 
    R: Trait3and4
](x: T): -> R
    return x.required_method(42)

My example with dependent type is a bit different tho.