4 ms·
The function call syntax is old and well-established. It is used in Ocaml and Haskell, for example, but comes from older languages that influenced them. Note t
by omaranto 6y ago
The function call syntax is old and well-established. It is used in Ocaml and Haskell, for example, but comes from older languages that influenced them.
Note that the problem in Ruby does not come from the syntax "f x" for functions applications, but rather from "f" being syntax to call f rather than just evaluating to f. This Ruby is mistake is typically not present in functional languages that use "f x" syntax for calls. Typically in those languages a function cannot have zero arguments, and for functions with no useful arguments you pass a dummy argument called "()". So "f ()", commonly abbreviated to "f()" is the syntax for calling a function of "zero" arguments in those languages.
- ledauphin 6y agothanks, this is very helpful. I think I can agree that special-casing functions that take no arguments is a reasonable approach (such things should be pretty rare), but I'm less sure I see the utility in not special-casing function call syntax in the first place. Generally, I think of function calls as "special" because they redirect active execution of the program to a different context. e.g., in: f a b the space between the letter "f" and the letter "a" means "transfer control flow to f, passing a", whereas the space between the letter "a" and the letter "b" means, essentially, "also". In a butchered English sentence, "transfer control flow to f, passing a also b". This seems far more explicit when looking at ALGOL (and following)-style f(a b) I'm curious what the argument for not special-casing general call syntax is, because I guess I've never run across a place where it was articulated!
- omaranto 6y agoWell, the "f x" notation for function calls comes from the tradition of functional programming languages, where functions calls form the bulk of the code. It makes sense to make the most common operation have the lightest syntax. Also, I suspect there is a misunderstanding in the "f a b" case. In the languages that use this syntax functions only have one argument, so that parses as "(f a) b". Both spaces means the same thing: evaluate a function; first evaluate f with argument a, which returns a function f a, then evaluate this function f a with the argument b. In these languages, what would be a two argument function in other languages, you would define instead in either of these two fashions: - As a function of a single argument which is a pair. So "f (a,b)" (which can be written as "f(a,b)" for short) calls f with a single argument, namely the pair (a,b). - As a function returning a function that uses the second argument, the "f a b" style, which means "(f a) b". These two forms are equivalent, different functional languages tend to prefer one or the other (SML, Ocaml and F# tend to use "f(a,b)", while Haskell tends to use "f a b"). Passing between one form and the other is called currying in one direction (I always forgot which one!) and uncurrying in the opposite direction.
- ledauphin 6y agoyep, this is what i was missing. I'd forgotten that it was common to be strict about functions always doing partial application, such that `f a` calls a function creating a partially-applied function. thank you for taking the time to clear this up for me!
- slightknack 6y agoSomething interesting is partial application. Say you have a function that takes two arguments: add = a b -> a + b This is really equivalent to: add = a -> b -> a + b Which in turn is equivalent to: add = a -> { b -> { a + b } } (for good measure). This means that the first call to add, when you pass a value `x`, returns a function (the `b -> { a + b }) that closes over x: add_5 = add 5 add_5 7 -- is 12 add_5 -1 -- is 4 This also has a unique property: most function calls are just chains of identifiers and values: foo bar "Hello" baz Which means, if we consider them as chains, we can match against those chains and apply transformations to them: syntax 'foo 'bar name 'baz { print name } Which is the basis of Passerine's macro system! (And don't worry, if the form matches multiple macros of if you use an identifier defined in a macro, it'll let you know). Hope this helps!
- ledauphin 6y agothis is great, thank you. In the languages I use more frequently, one reason partial application needs to be done more explicitly is because not all arguments are necessarily required. Frequently these languages support either maps or "keyword arguments" to solve the problem of allowing some arguments to be optionally specified with defaults. Is it common in this alternative style of language to have named arguments, such that you could still apply "optional" arguments in a non-positionally defined order by name? I'd love to be pointed to some information on how this "school" of languages handles problems where this is a less well-defined order of partial application.
- slightknack 6y agoSo Passerine has a record type, and is row-polymorphic. Names keyword arguments in passerine would look like: draw_arc { x: 3, y: 5, radius: 10, start: 0, end: 2 * 3.142, } As for defaults, this is something a bit more difficult. In a dynamic language, you can just try getting keys and replacing missing ones with the default ones. However, in a static language, this isn't quite possible. a missing field on a record is a compile-time error, not a runtime one. Even row-polymorphic type systems, which are generally more lenient with respect to records aren't a solution. Rust has `Default::default()` which allows one to splice in default values when a struct is constructed. This relies on the `..` splicing syntax; I've been considering something similar for Passerine, but I haven't come to any conclusions yet. I like this because it makes the fact that you're using defaults explicit, but at the same time I'm against it because it feels like unneeded boilerplate: draw_arc { x: 3, y: 5, ...Arc::default(), } Another option is to just defer field checking to runtime, and allow these errors to be handled. Given that I want to be able to compile Passerine to machine code at some point, I'm not sure whether this is a good idea: -- if the expression errs, replace it with default syntax expr 'default def { match (try expr) { Ok v -> v, Err _ -> def, } } draw_arc { x: 3, y: 5, } draw_arc = arc -> { full_arc = { x: (arc.x) default 0, y: ... } } But I'm not sure which avenue I should take at this point in time. > I'd love to be pointed to some information on how this "school" of languages handles problems where this is a less well-defined order of partial application. I know Ocaml has labels, which are basically public function names. so like: let f ~banana = a + 1; means that the type signature is something like: val f : banana:int -> int = <fun> Notice how `banana` is a part of the type signature. There are also optional named parameters: let f ?(banana = "ripe") = ...; Which when used have to be explicitly listed: f ~banana:"rotten" (); That's about it. What are your thoughts?