oh-lang was greatly inspired by JavaScript/TypeScript's ability to use option bags for function arguments. i work in a large code base where i regularly come across code like this:
// TS
myFunc(
id: string,
dogPrefix?: string,
enableLasers?: boolean,
enableSharks?: boolean,
elephantPrefix?: string,
) { ... }
and there will be callers that don't care about any argument besides some near the end, calling it like this:
// TS
myFunc('the-id', undefined, undefined, undefined, 'my-elephant-')
this is terrible for call-site readability. if you need to keep positional arguments for some reason, and you have a bunch of booleans, it would be better to make them enums like enum Lasers { DISABLED, ENABLED }, so that the call-site would look like this:
// TS
someOtherFunc('the-id', Lasers.ENABLED, Sharks.DISABLED)
this will be compile-time checked so that your call-site argument order matches the correct declaration-site argument order. but in my opinion, the best option is to use option bags, which allow you to do stuff like this:
// TS
myFunc(options: {
id: string;
dogPrefix?: string;
enableLasers?: boolean;
enableSharks?: boolean;
elephantPrefix?: string;
}) { ... }
myFunc({id: 'the-id', elephantPrefix: 'my-elephant-'})
which is much more readable, and doesn't require us to pass in arguments in the correct order. a couple downsides: the double parentheses situation ({}) doesn't look great, and i don't love how option-bags and enums work together:
// TS
myOtherFunc({
id: 'whatever-id',
mySpecialEnum: MySpecialEnum.WHATVER_YO,
...
})
there is a lot of redundancy here. additionally, when creating an object (either for a function call or something else), if your variable name lines up, great, but if it's a nested field, you again need the redundancy:
// TS
const firstObject = {x: 3.4, y: 5.6};
const y = 57.3;
const secondObject = {x: firstObject.x, y};
in oh-lang, we're going to try and fix all of these problems.
- we always pass arguments by name, so we don't need the double parentheses
({})situation, and we won't need to worry about argument order - the default-name for a type like
#my_special_enumismy_special_enum. if you pass in a variable namedwhatever: #my_special_enumto a function, we'll first look for an overload with awhateverargument, and failing to find it, we'll fallback to the default type name. - when creating an argument,
(some_object x)will by default resolve to(x: some_object x), and even similarly for function calls(some_object x())will be(x: some_object x()). we'll match the last field in whatever nested nightmare you come up with. if the function doesn't take an argument namedx, we'll consider it as a default-named argument of its type like the above point.
#my_special_enum: #one_of[unknown:, go_for_it:, whatever_yo:]
# declaration
calculate_trajectory(x: #f64, y: #f64, t: #f64, my_special_enum:): #null
{ print("calculating")
}
# call-site
x: 1.234
some_object: [t: 30.45]
y(): #f64 { print("calculating y"), 55.5 }
calculate_trajectory(y(), some_object t, x, #my_special_enum whatever_yo)
if you need to rename your variables for whatever reason, you can fallback to calculate_trajectory(x: 1.234, y: some_object x, ...), but standard argument matching should help reduce redundancy in code without sacrificing readability.
Comments
Post a Comment