3

I'm creating a programming language as a hobby, but I encountered a problem with designing its syntax. The problem is the conflict between the syntax for defining zero-argument functions and the syntax for reassignment operations. The function definition syntax is taken from Haskell, and looks like this:

name arguments = expression

A zero-argument function would obviously have this form: name = expression. However, that poses a problem because, without context, the programmer could confuse such a definition with a reassignment operation (which has the same form, variable = expression).

My solution is simple: ban functions with zero arguments. More precisely, such functions would now take a single value of the unit type as their argument. This is inspired by Scala, where functions that "don't return anything", actually return () (of type Unit), which is a type with only one possible value that carries no useful information.

The definition would then look like this:

name () = expression

And a call would have the form name () -- note that '()' is not a parameter list, but rather the value of type Unit. The above definition relies on pattern-matching, as () is not the name of the argument, but the only form it can take.

My question is whether such a design decision makes theoretical sense, and whether it would have some practical negative effects?

  • 2
    I don't see an advantage for myRandom () = new Random over myRandom = new Random() if I am reading you correctly. – AthomSfere Jul 11 '15 at 23:44
  • Isn't this exactly what Haskell does? Well, except that variable = expression defines an unchangeable binding rather than assigning to a variable. – user7043 Jul 12 '15 at 0:10
  • 2
    In imperative or object-oriented programming languages, functions without arguments are actually very common, so banning them would be a quite bad idea. On the other hand, they are far less common in purely functional languages. We don't know nearly enough about the design of your language to provide any helpful advise. I voted to close this question as primarily opinion-based. – Philipp Jul 12 '15 at 0:28
  • 1
    This is what you would do in [s, Oca]ml to declare a function taking no interesting parameters. unit -> foo is a common type when declaring "This has some side-effects and computes a foo" – jozefg Jul 12 '15 at 2:26
  • 1
    @ChristopherLord Not the case in my language: there are side effects. – jcora Jul 14 '15 at 8:16
4

Sure, that makes sense theoretically. Especially in functional languages, having a function that takes no input is weird. Though I would encourage you to prevent declaring parameterless functions, not prevent them in your type system.

Consider parameter binding. If you have a unary function and bind a parameter to it, you would have a parameterless function even though you did not declare it. Having a special case to add a dummy parameter seems not great (but perhaps unavoidable).

Just using a different declaration operator than the equal sign seems like it would also be a viable alternative to deal with potential confusion (though parameterless functions will have impact elsewhere in the language).

  • I don't quite follow the part about parameter binding. If f is a function T -> U, then f x doesn't just bind a value to the first parameter, it calls the function, because now all arguments are supplied. This is what several functional languages do. In fact, it is the only reasonable course of action that I can imagine with that function call syntax. – user7043 Jul 12 '15 at 0:54
  • @delnan - I was thinking along the lines of boost::bind in C++ world, where you can say bind(f, x) which returns a nullary function that returns U. – Telastyn Jul 12 '15 at 2:52
  • I see. But with this function call syntax, a better option is to go all the way with currying. Then no separate function is necessary and it becomes more consistent that binding the only remaining parameter calls the function. – user7043 Jul 12 '15 at 7:47
  • @Telastyn My language is not pure, and these parameterless functions would actually be used frequently (time, random, printNewline, etc). However, I'm not really buying your binding argument -- it would be simple to bind an argument to a unary function by using a lambda expression that transforms it into this "unit-accepting" function. – jcora Jul 12 '15 at 16:42
  • @jco - sure, it would be simple, but it is still a special case of binding. – Telastyn Jul 12 '15 at 16:52
2

Scala returning () really just means that you should ignore the meaningless return value. Basically the same as void in C, right? But a function that takes no arguments can still return a usable value, so I'm not sure I see a one-to-one correspondence in the design.

Forcing functionally parameterless functions to take a parameter of type Unit which is ignored by definition, feels really unnatural to me.

Lots of languages have survived overloading the = operator.

  • Well in this case, unit means that thhe value of the parameter is meaningless. And all functions are one-argument functions in this context actually, because of currying, so it does make sense. – jcora Jul 12 '15 at 13:36
  • @jco Umm, yep, value of unit is meaningless. ;-) The OP did say the function definition syntax was borrowed from Haskell, but did not indicate that the goal is a purely functional language with strictly deterministic functions, et al. If it is, that's one thing. But if it's more of a general purpose language, forcing the programmer to have to pass at least one (meaningless) parameter even to action routines has very unnatural semantics: ToggleSwitch( meaningless ) – Craig Jul 12 '15 at 17:38
  • I am the OP haha. No, it's not a pure language, that's the entire reason it even has functions without arguments or actions as you call them. It is a little unnatural, yes, but do you think it would be a problem? The syntax is toggleSwitch (), not toggleSwitch(meaningless). – jcora Jul 12 '15 at 17:54
  • 1
    @jco Oh, right (you're the OP)!! ;-) I was illustrating the semantics, not the syntax, but in this case the syntax actually makes a pretty big difference, doesn't it? You would require the space before the ()? You know, since somebody could choose to perceive that as an "empty set" rather than as an ignored/meaningless argument, which probably flows a little better for at least some folks and that thinking doesn't seem to impair any use cases, maybe it's not so unnatural, after all. – Craig Jul 12 '15 at 18:04
  • Yes, I agree that the semantics is essentially redundant, although more consistent with "all functions are unary" logic (currying). A space wouldn't be required, just like in an expression like 1+1 -- () is a separate identifier. – jcora Jul 12 '15 at 18:20
2

I have decided to go through with this design decision for the following reasons:

  1. Such a convention is actually more consistent with the functional nature of the language. All functions are unary (accept a single argument) -- those accepting multiple arguments are simply curried, and those accepting "none" (as per this decision) actually accept the sole () value.

  2. Several functional languages already implement this convention, such as Ocaml (see the section How to define a procedure?), which gave me more confidence that it's not just a random hack I came up with.

  3. The decision itself allows for the interesting Haskell-inspired function definition syntax to be used, because the ambiguity between reassignment is removed (which is the original motivation for the decision, see the question).

So the direct answer to the question is it doesn't not make sense theoretically, and has positive practical effects.

  • 1
    It doesn't not make sense indeed :). I was about to post an answer about OCaml, but I see you've already found it. In my experience with both Haskell and OCaml, it's a perfectly good system. – Tikhon Jelvis Aug 20 '15 at 0:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.