Porting this over as it is an off-topic discussion on the REPL thread that got out of hand. I won’t quote all the message, but two of the last ones are:
Also took the time to gather some data. As far as I can tell, def is fairly unpopular, at least among library authors, to the degree that it may be worthwhile to survey the Mojo community as to whether it’s actually a desired feature depending on the maintenance burden it imposes.
And my response:
I think the existence of def is tied deeply into the question of what Mojo is trying to be and which space it occupies in the programming language landscape. If you want to be poetic, I think it is a question about the “soul” of Mojo
I can totally see the argument against def: it’s an extra class of issues to deal with, and some of them can be quite gnarly, but I believe if Mojo is trying to achieve what I think it is trying to achieve then it is critical that def stays.
The def use case is mostly for “business logic” as opposed to library development.
This is a bit of an oversimplification, but I see Mojo going one of two ways:
Without def, Mojo effectively becomes the go-to language for writing fast performant libraries (especially in the ML/AI domain, but likely other places as well), that are mostly called from Python, i.e. replacing C/C++/Rust as the language that people use to write libraries for Python in. I.e. it doesn’t exactly solve Python’s two language problem, it just makes it a lot more palatable.
With def, Mojo does everything in point 1, but also replaces Python for the “business logic” side of programming, i.e. effectively solving Python’s two language problem (which as @JulianJS points out can be a real hassle). Python’s appeal (as can be seen by it’s popularity) is it’s ergonomics and ease of use, and without def it is hard to see how Mojo would compete on that front.
I think both of the above outcomes are positive, and achieving the first will definitely be easier than the latter, but I think the latter is worth attempting at least.
So yeah, go ahead and debate your hearts out in this dedicated thread.
From what I’ve seen, def is unpopular for standard library code (which is generally non-throwing, so def would be inappropriate) but very commonly used for testsuites (that often have a lot of raising functions), and an important part of the Mojo “max graph” api’s, which are more “pythonic” in terms of use-case.
I’m not aware of def causing any problems today, other than its tie-in to returning object by default (because object is so underbaked), and I do think it is an important part of building a bridge to more dynamic and flexible coding styles that Python supports.
I’d be +1 on removing the implicit object return (and removing object entirely) but I don’t see motivation to removing def.
If we want to support research, which is a big part of AI, we’ll want to support untyped code, there is no way around this as so many researchers don’t type their code. After this, in what form comes untyped code in mojo is another question. It can be with def or we can find some other way to allow it in the language.
I believe “Dynamic” typed def in combination with reference counted classes would cover the need for what Python programmers are looking for.. For performance oriented code there is always fn and struct. To make them work nicely together might be a bit of a challenge though. But I think it is worth it.
In Python we don’t have to specify return type in the signature and can return any value. I guess to get an equivalent functionality without having to perform flow analysis, the default for def was returning object.
Yes, but Python is dynamically typed. In case of Mojo, either it statically defines as returning object or does flow analysis to infer proper return type. This can get complex if the function returns more than one type of value. In that case Mojo needs to return Union. Or force us to define a return type in the signature. Python does not have this issue. Only when we don’t return anything in Python it defaults to None.
def is great when trying to copy paste python code IMO. Right now it’s not ideal because we need still to desugar a lot of things like dicts and list comprehensions, but help a lot! I still don’t understand why the default is object and is not PythonObject. The whole purpose is to have easy interop with python, but if I need to return a Python object, it’s not friendly. Maybe is just something I need to get familiar on how to these things the right way, but it doesn’t help for me. We need a different type? Well, we can define it on the signature, but by default, IMO it will help if it is PythonObject.
Ah yes. We probably want to do static type inference here; Typescript has a feature like that, as do Python’s static type checkers like Pyright. How complicated can it be? If it’s too complex then maybe it should be left to linters rather than being built into the compiler. However, I think the infrastructure to do this sort of type inference is already built out since you need the same flow analysis to verify that a function with an explicit return type actually returns what is specified.
The algorithm which Typescript uses is the same one that Swift uses. Swift has infamously bad compile times, mainly due to that particular algorithm being NP-hard in the presence of function overloading, something Typescript avoids.
For those not aware, NP-hard is the bucket computer science throws a pile of very, very nasty problems, namely ones that we’re not sure can be solved in polynomial time. We can brute force them to some extent, and if you have a large GPU cluster you can go further, but generally they are impractical to solve outside of small input sizes.
Mojo has function overloading, which it uses are part of function specialization, which is an important performance feature. @sazb did his masters on this problem, although he had to throw out a lot of type system features to make it work. Mojo has what could be a better shot, since it looks like most of the language will be statically typed, which greatly reduces the amount of computation required to use the algorithm for def functions, but it would effectively mean people would be forced to switch to fn after their codebase got large enough. Whether that is a good thing is a matter of opinion.
I am hoping that we can unify object and PythonObject. Ideally we have classes, objects that are Python compatible. Convertion from struct may need special handling like boxing.
I think def can do a simple inference that if there is no return statement with any type, then it returns None. Otherwise it returns object.
Oh yes, I kinda forgot Mojo has function overloading. That makes things a lot more complicated.
Typescript also has this half-baked overloading where you only overload signatures; in those cases you have to explicitly list out the function signatures I believe: Documentation - More on Functions
But like I said, this is making things a lot more complex, probably more work than it is worth.
We discussed this as a team, and agreed that we should decouple def and object for now. We’ll change def foo(): to default to returning None and change def foo(a): to be an error due to missing type annotation (instead of defaulting to object).
def will remain otherwise for the immediate future.