There's no formal guarantee that they produce the same instructions, because there's no formal guarantee even that the same code produce the same instructions across separate builds (even with identical toolchains and configurations--for example, with the same version of GCC and all the tools it uses and the same command-line options passed to the
g++
command). Also, I should note that the binary files are not identical because the symbols that are emitted, for use by linkers, are different.
...But the implementation of the functions comes out the same, which is entirely to be expected, and it's a good thing.
To be clear, we're both talking about how the version with function overloading and the version with a function template produced the same instructions as each other, right? In contrast, in each case, there are two actual functions, and those functions have different assembly instructions, because one of the functions multiplies
int
s and the other mutiplies double
s. (I just want to make sure we're on the same page.)
So, in a dynamically typed language with duck typing, like Ruby or Python, a function is automatically capable of handling arguments of all different types, provided that whatever objects are passed as arguments are capable of responding to the operations that are invoked on them. If called with arguments that can't do that, a runtime error occurs; if called with arguments that can but the operations are interpreted in a way that doesn't do what you want, you get a logic error (a runtime bug).
9:01 AM
@EliahKagan you wrote two programs, and they both have two functions called
multiply
, one for dealing with integers and one for dealing with doubles. You wrote the programs differently, but the compiler explorer showed that they produced the same instructions as each other. The instructions for the multiply
function for integers came out the same from program 1 and program 2, and the instructions for the multiply
function for doubles came out the same in program 1 and program 2
@EliahKagan For example, in Ruby, you can multiply an array of strings by a string. This joins the strings in the array given as the first operand to
*
into a single string, such that they are separated by the second operand to *
. For example, %w[foo bar baz quux] * '__'
(which can also be written ['foo', 'bar', 'baz', 'quux']
, as %w
in Ruby is like qw
in Perl) evaluates to 'foo__bar__baz__quux'
.
The elements of the array need not even be strings. Any non-string element has its string representation used. If you had a function that multiplied its operands -- and did other stuff, because who would write a function that just multiplied its operands when you can just use the
*
operator directly! :) -- and you ended up accidentally passing an array and a string, then it would do that.
@EliahKagan by "[provided that] arguments are capable of responding to the operations that are invoked on them", you mean that I can't write
elephant/giraffe
, because an elephant isn't capable of being divided by a giraffe (maybe a family of elephants could be divided by the machinations of a giraffe, but the interpreter won't be distracted by such silliness)?
@Zanna That's one way, yes. It could also just not be defined for that operation at all. For example, if one of the things a function does every time it is called is to invoke it's argument's
foo
method, and you pass something with no foo
method, then that, too, will produce a runtime error. The corresponding logic-error case to that would be if you passed something that did have a foo
method, but whose foo
method didn't do the same thing as the intended arguments' foo
methods.
@EliahKagan This is worse than immediately getting an error. Likely it would result in an error eventually, but it might be hard to figure out what caused it. In contrast, in C++, an extremely high volume of incomprehensible error messages from code far away from where the actual bug happens at compile time.
@Zanna I'm not sure which part you mean, but if you're talking about joining strings with
*
, then while I don't recommend refusing to use that syntax, I recommend keeping in mind that there is a join
method that, at least some of the time, might make more sense later when you or someone else reads the source code (e.g., %w[foo bar baz quux].join('__')
produces the same result as %w[foo bar baz quux] * '__'
).
In C++, the compiler produces multiple, separate implementations. So sometimes your program is bigger than you might think, just looking at the source code, if it uses templates heavily. Compilation can also take a long time. However, because the compiler generates separate functions, type checking is done completely during compile time, and information about the types of the template parameters doesn't have to be emitted into the executable code.
9:25 AM
Also, the separate functions produced by instantiating the function template can be separately optimized. (C++ is all about usually being able to produce code that is reasonably fast while usually keeping the ability to work with abstractions. That is, the idea is that you can write that is high level but nonetheless performs well. There's a lot about C++ that is really excellent. The biggest downside of C++, I would say, is that is very complicated.)
1 hour later…
11:44 AM
12:53 PM
If you're interested in statically typed compiled languages, the language I was going to suggest was Kotlin. I don't actually know Kotlin at all. I tried it out a while ago, though, and was impressed. It supports the common styles of programming--procedural, object oriented, functional, generic--well.
Its most mature and popular implementation targets the Java VM (and Kotlin is designed largely for that), so many situations where you might consider Java or Scala are use cases for Kotlin, too. It was designed by developers at JetBrains, and it's supported by IDEA (their IDE).
Android Studio is currently based on IDEA, so it supports it too. Kotlin is fully and officially supported for developing Android apps. (This doesn't quite follow from it targeting the Java virtual machine, as Android uses a different virtual machine.)
Kotlin's type system is really nice. You don't dereference nulls unless you really try, because the type system makes information about whether or not a reference can be null available at compile time and uses it to enforce correctness.
There's a mechanism to explicitly force a nullable reference to be converted to a non-nullable reference (throwing an exception if that fails), but it's rarely necessary and--based on my brief try with the language--not even tempting to do that.
Kotlin has type inference, a compact syntax for declaring mutable variables (so that people will actually do it), and it fully support object-oriented programming whilst still not trying to force it down developers' throats.
Kotlin has local functions (you can write a function in a function) and lambda expressions, and they capture variables from the surrounding scope. This doesn't require the use of any extra, complicated syntax, and it allows you to modify the variables (unless they were declared immutable).
It has flow-sensitive typing ("smart casts"), which is to say that, in branches of code that are selected based on a dynamic determination of an object's type, that additional type information is reflected in the static type of expressions consisting of or otherwise containing the variable whose type was checked.
There are other languages with these features. There are even some with all of them (or that have features that are similar and just as good). However, I am not aware of any languages that have these features and support all the common styles of programming. Recent versions of C# have many of these features, but not all. I like C# better than Java, but if Kotlin had existed when I learned C# (it didn't), I would either have learned Kotlin instead or I would wish I had.
1:34 PM
To be clear, languages like Scala and F# don't prevent programming in non-functional styles. But they're designed to support functional programming specifically (especially F#, which is similar to OCaml). Kotlin is often described as a better Java. I agree with this. People usually mean that just positively, but I'd add that it does have some limitations that come from the Java virtual machine.
Unlike in Java, you don't have to distinguish between boxed and unboxed primitive types in your code in Kotlin. But there is still a performance penalty associated with boxing, and generic collections still don't support primitive types.
Generics are still not reified as they are in the .NET Framework and Mono (which C# and F# target), which is the main thing I like better about those languages than about any language that targets a current Java VM (though I have hopes for the future).
I should perhaps add that Scala has many of these benefits and it's quite possible to program in an object-oriented or procedural style with it, so my preference for Kotlin may be aesthetic or related to its greater similarity to C#. (There are some major advantages of Kotlin over Scala--Kotlin compiles fast--but as someone who so far does not even use the language, that's not one of the factors that affects my views of it.)
2 hours later…
3:25 PM
« first day (211 days earlier) ← previous day next day → last day (2188 days later) »