The Wikipedia article Function composition (computer science) says:
Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole.
I have two questions about it:
A composable function must have both arguments and return value?
So following functions are not:
def doNothing(): Unit = ()
def myName(): String = "My name"
def eat(food:String): Unit = ()
Is my understanding correct?
Can this function side-effect?
def hello(name:String):String = {
println("name: " + name) // side-effect
name + "!"
}
Do we still consider it as "composable"?
The mixture of formal language from math with more colloquial language from programming makes these conversations difficult. You're dealing with two contextually-loaded words here: "composable" and "function".
The mathematical notion of a "function" A → B
is a mapping from some set A to some set B, and "function composition" is a specific operation denoted by ∘
. For some f: A → B
and g: B → C
, g∘f
is a function A → C
such that (g∘f)(x) = g(f(x))
for all x
in A
. This composition is defined for any two functions if their domain/codomain match up in this way (in other words, such a pair of functions "can be composed"), and we describe this by stating that "functions are composable".
As a qualitative term, we use "composability" often in software to describe the ability of a set of compositions can build large things from combining small ones. In this sense, programmers describe functions (as a whole) as "very composable", because functions can (and, in a purely functional language like Haskell, do) comprise the large and the small of an entire program.
In software we also see a more human-oriented usage of the term "composable" which tends to be associated with "modularity". When components are stateless, concerns are separated, and APIs have low surface area, it's easier to compose programs without making mistakes. We praise the components of such a design as being "composable"—not just because they can be combined, but because they're easy to combine correctly.
I'm going to use the slightly outdated term "subroutine", because I don't know a good way to discuss this in the parlance of our times. If a subroutine doesn't do any IO (and always halts, and doesn't throw…), then it implements (or "is") a "function" in the mathematical sense. IO subroutines have superficial resemblance to functions, because they may have input and output values, but the similarity stops there. None of the conversations we may have about "function composition" as we first discussed it will apply.
Here's where we hit the trickiest linguistic difficulty, because the word "function" has come into common usage to refer to any subroutine, even ones that perform IO. FP enthusiasts tend to fight this—people say things like "if it does IO, it isn't a function"—but that battle of popularity has been lost and there's no turning back now. Within most programming contexts, all subroutines are called "functions", and the only way to distinguish functions that satisfy the mathematical definition is to call them "pure functions".
With these definitions established, let's address your questions:
There are a couple boring things to point out about this question. First, every function in Scala technically has a return type. If that type is Unit
, it may be elided for brevity, but it's still a return type.
And a nullary (0-arg) function can be trivially transformed into an equivalent function with an argument. So really, it just doesn't matter. If you're in a situation where you need to compose functions with arguments and f
has no argument, you can just write _ => f
.
Merely a semantic squabble. In the context of Scala, the most appropriate thing to say is that it is a Function
(or perhaps technically a "method", depending on where it is defined), but due to the side effect, it is not a pure function.
Sort of. All of these things still "come together" in a fairly general way, so yes, they do compose in the software sense. Although pure functions compose better than impure ones. And the mathematical notion of function composition does not apply to subroutines that are not pure functions.
Finally, if you want to know whether they literally compose in Scala with the compose
method on Function1
, you don't need Stack Overflow; just ask the compiler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With