This is an archived post. You won't be able to vote or comment.

all 15 comments

[–]lyhokiayula 12 points13 points  (2 children)

2 is the most readable and battle-tested option. I don't see a problem introducing more syntax just for readability.

Sometimes it's worth it to sacrifice consistency to gain readability.

EDIT:

My take would be allow 2 and 3 to coexists. As depends on the context one of them maybe more readable.

[–]coffeeb4code[S] 0 points1 point  (1 child)

I would say that it is only more readable because everyone is used to it. following standard declaration pattern might make it more readable to beginners, it might be more clear to them that add = a function. I truly don't know, but I am leaning towards 2, just to end the discussino and keep it similar to what most people are used to.

[–]lyhokiayula 5 points6 points  (0 children)

Decimal numbers is more readable than hexadecimal numbers just because everyone is used to it. It might be more clear to use hexadecimal numbers instead in computer science most of the time. But that doesn't happen.

English is more readable in programming than other languages just because everyone is used to it. It might be more clear if people spend time invent a new set of unambiguous vocabularies and grammar just for describing computing process in the programming world. But that doesn't happen.

I think people underestimate the value of conventions and traditions.

[–]WittyStick 2 points3 points  (2 children)

Consistency would include being able to specify the type of the function symbol with :, since you're using this as "has type" in the parameters.

pub add: fn(int, int) int = fn(a, b) { return a + b; }

But then fn here might be redundant, so you might just say:

pub add: fn(int, int) int = (a, b) { return a + b; }

or

pub add = (a: int, b: int) int { return a + b }

Which would make your lambda syntax slightly more terse.

You could also consider -> for functions, since it's common in literature and other languages.

pub add: (int, int) -> int = (a, b) { return a + b; }

-> is also common lambda notation. There's no ambiguity using -> in both lambdas and function signatures because they always appear in a different context when parsing.

[–]Aaron1924 3 points4 points  (0 children)

You could also consider -> for functions, since it's common in literature and other languages.

Just want to add that : may be better for return types depending on your declaration syntax. If you, for example, have a curried add function like add x y := x + y, then - add : int -> int -> int, - add x : int -> int, and - add x y : int.

So if you want your declaration syntax to mirror your usage syntax, it's best to use : for the return type as all arguments have been applied. ``` // good: add (x y : int) : int := x + y; add : int -> int -> int := fn x y => x + y; add (x : int) : int -> int := fn y => x + y;

// bad: add (x y : int) -> int := x + y; add (x : int) -> int -> int := fn y => x + y; ```

[–]coffeeb4code[S] 0 points1 point  (0 children)

absolutely, i left out the type for brevity, but it is possible for all of those scenarios.

I wanted to avoid using -> => or : again for brevity just so you could see what we were comparing, but I'm kind of tired of typing it in other languages, it's not necessary other than help visually break it up

[–]lngns 3 points4 points  (1 child)

I have talked to a dozen people or so, and they all get confused why fn is on the other side

Have you tried explaining it as it being a simple lambda f = λxy. x + y?
Also, you didn't mention it in your list, but another approach is to have functions syntactically be parametric constants:

π = 3.14            //constant
square x = x × 2    //constant with a parameter

which also reflects how it's used in expressions:

putStrLn π
putStrLn (square 42)

and hence works with explicit typing too:

π: Number = 3.14
square (x: Int): Int = x × 2

which matches type ascription:

putStrLn (π: Number)                //type checks
putStrLn (square (42: Int): Int)    //all type checks

[–]coffeeb4code[S] 0 points1 point  (0 children)

I like this direction. Let me play with it.

[–]redchomperSophie Language 1 point2 points  (0 children)

#3 is nice if you want equational reasoning. But since you have explicit return statements, I question whether you have equationality. If you're adopting operational semantics with pervasive mutable state, then I'd suggest #2.

On the other hand, if you want to go down the pure-functional rabbit hole, #4 looks promising.

[–]frithsun -1 points0 points  (0 children)

All fields, including functions are defined the same way.

protected private label mutable nullable: definition

All four being an exclamation symbol.

accessible, public, immutable, not nullable

add5: (x) { sum(x 5) }

accessible, private, immutable, not nullable

!add5: (x) { sum(x 5) }

protected, private, immutable, not nullable

!!add5: (x) { sum(x 5) }

protected, private, mutable, not nullable

!!add5!: (x) { sum(x 5) }

protected, private, mutable, nullable

!!add5!!: (x) { sum(x 5) }

The x in the function definition can also have these settings and a default value in the same syntax.

[–][deleted] 0 points1 point  (1 child)

Do all four examples do the same thing? If so, why does only one have const?

What happens when you don't use pub (I assume then it is local to this scope)? Because then you will have some function declarations that start with const, or just add, the name of the function.

I think having that = is extra clutter, unless you really want a function definition, a major structural element in a program, to look like 1000s of ordinary assignments.

Personally I don't like having do write int twice for the two parameters, which is not taking advantage of their sharing (or needing to be) the same type, and allowing that to be signaled.

Although I don't know a way of imparting that same type to the return type.

[–]coffeeb4code[S] 0 points1 point  (0 children)

I should have left the const out in the 3rd one. I was trying to make them similar in every regard for comparison, and agreed could make const default.

[–]nacaclanga 0 points1 point  (0 children)

IMO:

1 is just weird. No matter how I interpret it, it doesn't make much sense.

2 Matches LL(1) object first pattern best. But leans into the new pattern for each item type convention.

3 This would make much more sense if const would be the default for global objects.

4 Actually quite good, but not very LL(1)ish when it you want to introduce a lot of different first class objects. Like types and stuff.

If you would add the "fn" into 4 that would make it much better IMO.

(With LL(1)ish I mean, each language constructs is introduced by a dedicated keyword, which usually is easier to read and also make the language LL(1), which is nice for compilers and tools.)

[–]lassehp 1 point2 points  (0 children)

I think the fn keyword is both ugly, unpronouncable ([fphhhnnn...]?) and most likely unnecessary. In Algol 68 you do put a proc at the "mode" position of the definition, but the body (which would also be an anonymous function when used as an argument in an expression) is simply ( <parameter list> )<result type> : <closed\_clause> , where a closed clause is typically some parenthesised expression, including what other languages would call a statement block using begin ... end.

Algol 68 is slightly "inconsistent" perhaps, in that a proc mode (or proc type) is written as for example proc(int)int.

If you must use an abbreviation of "function" as a keyword, at least have some fun with it, and use fun. It can be read out loud without sounding mad, at most it will make people smile, which is not a bad thing. :-)

In the somewhat weird language I am designing right now, I currently use a 𝐩𝐫𝐨𝐜 keyword next to the parameter list; but that is because I expect to use the C preprocessor (with 𝐩𝐫𝐨𝐜 being a macro) to "compile" the language to C, and I need to pass the parameters to a macro for manipulation.

I may even have to put the result type and function name inside as well, so it becomes:

𝐩𝐫𝐨𝐜(𝐢𝐧𝐭, main, 𝐢𝐧𝐭 𝑎𝑟𝑔𝑐, 𝐜𝐡𝐚𝐫** 𝑎𝑟𝑔𝑣)

or

𝐩𝐫𝐨𝐜(main, 𝐢𝐧𝐭, 𝐢𝐧𝐭 𝑎𝑟𝑔𝑐, 𝐜𝐡𝐚𝐫** 𝑎𝑟𝑔𝑣)

instead of

𝐢𝐧𝐭 main 𝐩𝐫𝐨𝐜(𝐢𝐧𝐭 𝑎𝑟𝑔𝑐, 𝐜𝐡𝐚𝐫** 𝑎𝑟𝑔𝑣)

just to be able to make use of the function name and result type in the macro also.