I am learning about pure functions. The definition I have is:
A pure function is one in which the same result is always produced with no side effects given the same input.
This comes from Category Theory for Programmers by Milewski.
The context of the discussion was about the difference between mathematical functions (i.e. how one value is mapped to another) and programming functions.
Particular to this discussion was also on Haskell’s position where all functions written within it are pure functions.
In that vein, what I was wondering is if Julia can be considered a “pure” programming language? Edit: Can programming in Julia be “pure”?
Let me give an example:
In Haskell, one example of a pure function would be the factorial function:
fact x = product [1..n]
Which when translated to Julia is:
fact(x) = (*).(1:x...)
So, in this sense, are both of these functions pure?
To me, as the Julia implementation does not invoke side effects (that I am aware of), it appears to be pure.
Or am I misunderstanding how Julia computes functions?
What I think I am missing in my understanding is what does “pure” even mean in the context of programming?
I was under the impression that pure functions was the name of a group of functions but it seems more like “pure” is a bigger definition than I was aware of.
Furthermore, in terms of side effects, was how I understood them was that if I used my fact functions, for example, the only thing that should happen is a direct mapping of, say, fact(3) to the value of 6.
Nothing else in this implementation should occur.
CC @kristoffer.carlsson and @Mason - trying to clarify a bit more on my understanding but I think the definitions I was provided were a bit too vague.
Is there better/reasonable language I can use here?
I don’t think in any of the definition of a pure language would Julia itself be considered a pure (or functional) language. It is an imperative language and makes heavy use of e.g. mutation. You can of course write pure (for some definition of the word) functions, say using e.g. map and filter.
Ah ok - that makes sense.
Just to make sure we are using the same vocabulary, in your answer here, how are you defining “pure”?
Would my example of fact (written in Julia) fall under your definition for a “pure” function?
Sorry for the ignorance, why map and filter have anything to do with this? Isn’t any function that 1) does not mutate input parametes; 2) does not use global variables, pure? What is the concept that must be further studied here to understand why you mention map and filter in this context?
Wonderful!
Thanks for the explanation @kristoffer.carlsson - this was super helpful.
Also, thanks for asking the additional question @lmiq as I was also wondering the same thing.
As a result of this discussion and wanting to improve discoverability, I am slightly changing the discourse post title to better reflect what we discussed.
You can write functions in Julia that are “probably” pure, like this:
myfunc(x, y) = x + y
But there’s no guarantee that myfunc is actually pure, because it depends on the various + methods that have been implemented. It’s possible to implement an unpure +(::Foo, ::Bar) and then if you call myfunc on a Foo() and a Bar(), then your myfunc is no longer pure.
Thanks @CameronBieganek for this perspective as well.
I am realizing that additionally, “pure” in a programming sense seems rather vague.
This is a really good perspective that I wasn’t really considering.
Programming purity seems quite complex the more you delve into it.
I like the notion of “probably” pure as I think it encapsulates a more pragmatic approach to the language internals of Julia.
It lets one get away without having to necessarily be completely aware of all the internals of Julia when discussing purity.
There is always an idealization going on. If you call pure Haskell but in such a way as the computation requires more RAM than is available the Linux kernel will kill the process, or maybe reap another process… This is a side effect. Because of that side effect perhaps your robot falls off a cliff and catches fire. Then the result of your Haskell code is not deterministic or Pure.
Referential transparency also helps the compiler (and user) reason about what exactly ‘=’ means, This is useful not only for various compiler optimizations, but also for implementing various types of ‘higher order’ semantics, mathematical or not.
In the section: 2.3 FUNCTIONAL EQUALITY AND COMPOSITION (page 18).
Note that you do not need a purely functional language to accomplish similar things, either by inference, lifting, or other language mechanics, implicit or explicit.
Idiomatic Julia style mixes functions with and without side-effects. The former are easier to reason about and lead to cleaner code, while the latter can be very convenient in practice for some problems.
The right question is not whether a practical nontrivial Julia program can be fully “pure”, but whether it would be advantageous. The answer is that in most situations it does not make sense, because you would be working against the language.
And yet, in situations where it is necessary to reason about a fairly complicated algorithm and referential transparency is needed, it is possible to program in such a style and avoid complexity as Julia fully supports this style.