Nim supports procedure overloading that allows the implementation of multiple procs with the same name distinguished by the signature of their parameters. However, it does not seem to be possible to do overloading with explicit parameters such as [], "Araq" or 0 as in the following example:
proc fib(0): int = 1
proc fib(n: int): int = n*fib(n-1)
=> Error: redefinition of 'fib'; previous declaration here: /usercode/in.nim(1, 6)
Is there any reason why this isn't possible in Nim? Languages like Haskell or Elixir go through all functions / procedures from top to bottom and choose the first implementation that matches
defmodule Foo do
def fib(0), do: 1
def fib(n), do: n*fib(n-1)
end
IO.puts Foo.fib(6)
=> 720
proc fac(0): int = 1
thats not a valid nim.
it's because Nim procecedure overloads are determined at compiletime, so it's not possible to switch between them based on a runtime value (the exception being the deprecated methods).
Function programming languages have compiler smart enough to look through these small functions with overloads on runtime parameters, to then generate reasonable (or just slower) imperative code. In any case, this is an abstraction away from how computers actually work, from where the concept of a proc how the Nim compiler and procedural/imperative programming languages sees it is derived. It's not called proc in Nim for no reason, as it makes more sense to call it a procedure or a subroutine than a function. In Nim there's also func, which is more like a mathematical function, because it disallows side effects, but it's still not there.
I guess it's just semantically not how Nim works.
A proc has 1 body. However, if you use static/generic parameters then different versions of the proc body will be generated at compile-time based on the input.
proc say(n: static int) =
when n == 1:
echo "static one!"
else:
echo "static " & $n
proc say(n: int) =
echo "dynamic " & $n
var x = 3
say(1) # --> static one!
say(2) # --> static 2
say(x) # --> dynamic 3
(under the hood I believe 3 different procedures will be generated here, two of them are different variations of the first proc)
But you can't implement your factorial example with this, because choosing whether to return '0' or 'n*fac(n-1)' is a runtime decision.
it's because Nim procecedure overloads are determined at compiletime, so it's not possible to switch between them based on a runtime value
Thank your for this information. Nim drew inspiration from a multitude of programming languages and I've already read that it is more on the imperative side. When learning a new language I like to experiment with such "corner cases" to better understand a language an the design decisions behind it.
That's not overloading! That's how some (e.g. Haskell) languages define function by cases, like
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
It's equivalent to OCaml's
let rec fact n =
match n with
0 -> 1
| n -> n * (fact (n - 1))
where the explicit use of match helps you see it's not overloading.
What I would like from FPLs and Rust, is algebraic data types (yeah, I know about Nim macros to emulate them) instead of object variants, but that's too big a change at this stage of Nim's existence.
What I would like from FPLs and Rust, is algebraic data types (yeah, I know about Nim macros to emulate them) instead of object variants, but that's too big a change at this stage of Nim's existence.
For me
Am I missing something?
https://cs242.stanford.edu/f19/lectures/03-2-algebraic-data-types#variants
I'm surprised noone answered OP's question so far. So going back:
proc fac(0): int = 1
proc fac(n: int): int = n*fac(n-1)
Since Nim is not a functional, but imperative language, you would use runtime flow control in this case:
proc fac(n: int): int =
if n == 0: 1
else: n * fac(n - 1)
I'm surprised noone answered OP's question so far.
The question was why the code shown by the OP wasn't possible in Nim. :-)
Since Nim is not a functional, but imperative language,
I think we shouldn't think too much in language categories. If a language is mostly imperative, this doesn't mean that it can't have features that are typically found in functional languages. For example, several "imperative" languages have list comprehensions (or something similar), "even though" this is a feature coming from functional languages, as far as I know. Or think about Nim's side effect tracking.
Just to clarify: I'm not suggesting that Nim should support every feature from other programming languages. (I'm glad it doesn't.) But I suggest being more open and not avoiding to think about features in Nim because "they're functional language features." I think you could potentially have "every" feature in Nim as long as it fits within the overall design.
My understanding is that, in functional languages that use pattern matching, the different functions with the same name get merged together into one function by the compiler, with a switch/case expression to distinguish between the different cases.
So Haskell's
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
theoretically compiles into something very much like Nim's
proc fac(n: int): int =
if n == 0: 1
else: n * fac(n - 1)
As others have already said, it's runtime polymorphism, as opposed to the compile-time polymorphism of Nim's (or C++s, etc.) function overloading. But you can do the same in Nim, it's just a syntactic difference.My understanding is that, in functional languages that use pattern matching, the different functions with the same name get merged together into one function by the compiler, with a switch/case expression to distinguish between the different cases.
Think of it as just a special syntax that Haskell and some other languages have to define the function. There is no polymorphism or anything deep going on. Just a special syntax.
As others have already said, it's runtime polymorphism, as opposed to the compile-time polymorphism of Nim's (or C++s, etc.) function overloading.
It's no kind of polymorphism at all, just a syntax for Haskell to define functions. It's no different from the OCaml with a pattern match. Nim loses nothing by not having a syntax to do definitions this way; I never missed this in OCaml. Haskell's "overloading" is from type classes, though strictly speaking that is not really overloading. That is something I missed in OCaml.
Procedure overloading is the ability to define different implementations of procedures with the same name. Each of these procedures shares the same name but accept different parameters. Depending on the arguments passed to the procedure, the appropriate implementation is picked by the compiler.
In my interpretation fac(0) satisfies this definition
How, odd, because it surely doesn't; 0 is not a parameter. Parameters are typed symbols that abstractly represent the values passed to a procedure; they are not values and are in a different conceptual space from values. fac(0) is an invocation of a procedure, not a definition of one.
The quoted text could be clearer by saying "accept different numbers of parameters or parameters of different types".
This really isn't a matter of functional vs. imperative languages, it's a matter of basic language syntax. Functional languages need not offer Haskell-style matching syntax, and imperative languages could. Note that, in Haskell, you have to provide a type signature in addition to the pattern matching; the type signature is comparable to procedure signatures in Nim and other languages. Note that Haskell's
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
looks nothing like your
proc fac(0): int = 1
proc fac(n: int): int = n*fac(n-1)
The Haskell has a type signature and then two lines that match the value of the argument. Your first line is a mishmash of the two concepts and fails to identify the type of 0. No language works like that because it would be terrible language design.
the different functions with the same name get merged together
What different functions? There's only one function given in your example, the one defined by the type signature:
factorial :: (Integral a) => a -> a
It's quite wrong to interpret
factorial 0 = 1
factorial n = n * factorial (n - 1)
as function definitions--they are patterns, very similar to
(a, b) = iReturnATuple()
in Nim, which is a limited form of pattern matching.
As others have already said, it's runtime polymorphism
Anyone who says that is quite wrong. It's no more "runtime polymorphism" than a case statement is.
Since Nim is not a functional, but imperative language, you would use runtime flow control in this case:
proc fac(n: int): int =
if n == 0: 1
else: n * fac(n - 1)
There isn't anything non-functional about that.
magine swapping out Nim's object variants for Rust's enums. I think that would be an improvement.
Araq has explained on several occasions why he designed Nim's object variants the way he did. A quick google produced this comment (not from Araq):
magine swapping out Nim's object variants for Rust's enums. I think that would be an improvement.
Araq has explained on several occasions why he designed Nim's object variants the way he did.
Well you're both right. :-) The current design has its advantages but Rust's seems nicer. However, Rust's only works with more borrow checking, IMO. I couldn't copy Rust's design for Nim's case objects because back then Rust didn't exist. I knew ML's but ML's works because of its immutability.