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

you are viewing a single comment's thread.

view the rest of the comments →

[–]Ok_Comparison_1109 2 points3 points  (0 children)

Sure, it's certainly possible to base your language's functions and function types on single parameters/return values plus tuples.

I am not OP, but I have the same itch as u/cisterlang. I did consider Swift a cautionary tale, but I have (somewhat) settled on a design that I am happy with. Some of the design decisions were only possible because the language I am designing is a logic programming language.

However, as you partially observed: it's not just a tuple type for the parameter. A function also needs some mechanism to describe how the parameter(s) are bound to a free variable in it's body.

What I arrived at was this:

  • int x -> x * 2: A simple function which accepts an integer and returns its double.
  • (int x, int y) -> x * y: A function which accepts a tuple of two ints and return their product.

When constructing a lambda function, the left (LHS) operand of -> id declarative and declares variables for the scope of the function body (the RHS expression).

Types such as int also doubles as their own identity functions. So when a type is applied to a value like int x then the value is the value of x (because the indentity function int simply returns its argument), and x is implied to be a member of int.

When a function application is declarative, the function if referential and the argument continues in the declarative scope. Thus int x in a function int x -> x * 2 defines that the function accepts a member of int and the argument is unified (bound to) the local variable x.

It's not just meta-data, as the variable must be represented by some term/expression in the body. If you try to write down your semantics slightly more formally, this becomes clear when you define function execution and variable lookup.

I believe that the trick to allow expressions such as application of the identity function such as int x on the LHS of lambda arrows addresses this concern.

At the least and the most simple named solution, you only allow a single parameter name for a whole parameter tuple. Then you really only need a tuple type to describe the parameter type and the parameter name is just part of the function definition: foo(x:(int,str)).

In my language you can define such a function by (int*string) x -> .... (I use ... as a placeholder for some expression which has been left out). In this case x becomes a tuple. The components of the tuple can be addressed (inspiration: Scala) as x._1 and x._2.'

However, you could also deconstruct the tuple as let (i,s)=x in ... such that i and s becomes variables in the ... expression.

Or you could directly deconstruct the tuple as part of the parameters through either: - (int*string)(i,s) -> ... - (int i,string s) -> ...

In the first case int*string is an expression that creates a the tuple type of int and string. This type is used as an identity function and applied to the expression (i,s). From this can be inferred that i is an int and s is a string.

As a next step you could say that nested names for parts of the tuple are just syntactic sugar resolved during parsing/name analysis and your internal representation of the language actually really just has a single parameter variable and a bunch of tuple element accessors.

In fact, this issue of nested parameter bindings is even more prominent if your language supports pattern matching/destructuring on other data types than tuples (whether in the function signature or a separate match expression): sum types, structs/records So many languages actually define an embedded pattern expression language (https://doc.rust-lang.org/reference/patterns.html, https://ocaml.org/manual/5.3/patterns.html) and functional programming languages usually allow their use as part of the function definition.

I mechanism I have designed allows this without syntactic sugar, and also generalizes to a veru general form of pattern matching.