It was odd to me, because it hadn't really occurred to me before that, given infinite memory (and within a mathematical framework), there's fundamentally not necessarily a difference between a "list" and a "function". With pure functions, you could in "theoretical-land", replace any "function" with an array, where the "argument" is replaced with an index.
And it makes sense to me now; TLA+ functions can be defined like maps or lists, but they can also be defined in terms of operations to create the values in the function. For example, you could define the first N factorials like
Fact == <<1, 2, 6, 24, 120>>
or like this: Fact[n \in Nat] ==
IF n = 0 THEN 1
ELSE n * Fact[n - 1]
in both cases, if you wanted to get the factorial of 5, you'd call Fact[5], and that's on purpose because ultimately from TLA+'s perspective they are equivalent.[1] At least sort of; they superficially look like functions but they're closer to hygienic macros.
You don't even need infinite memory. If your function is over a limited domain like bool or u8 or an enum, very limited memory is enough.
However the big difference (in most languages) is that functions can take arbitrarily long. Array access either succeeds or fails quickly.
For some definition of quick. Modern CPUs are usually bottlenecked by memory bandwidth and cache size. So a function that recomputes the value can often be quicker than a look up table, at least outside of microbenchmarks (since in microbenchmarks you won't have to compete with the rest of the code base about cache usage).
Of course this depends heavily of how expensive the function is, but it is worth having in mind that memory is not necessarily quicker than computing again. If you need to go to main memory, you have something on the order of a hundred ns that you could be recomputing the value in instead. Which at 2 GHz would translate to 200 clock cycles. What that means in terms of number of instructions depends on the instruction and number of execution units you can saturated in the CPU, if the CPU can predict and prefetch memory, branch prediction, etc. But RAM is really slow. Even with cache you are talking single digit ns to tens of ns (depending on if it is L1, L2 or L3).
Optimizing for RAM access instead of CPU instruction speed can make your code magnitudes faster.
I meant in most languages functions aren't guaranteed to return in finite time at all.
Arrays and functions may be mathematically equivalent but on a programming language level they are practically different.
In the specific situation, let’s say that by an array we mean a finite, ordered list whose entries are indexed by the numbers 0, 1, …, n - 1 for some natural number n. Let’s also say that two arrays are equal if they have the same length and the same value at each position (in other words, they have “the same elements in the same order”).
If we now want to represent a function f as an array arr such that f(i) = arr[i] for every possible input i of f, then this will only be possible for some very specific functions: those whose domain are the set {0, 1, …, n - 1} for some natural number n. But for any two such functions f, g : {0, 1, …, n - 1} → t, their extensional equality is logically equivalent to the equality of the corresponding arrays: you really can check that f and g are extensionally equal by checking that they are represented by equal arrays.
But for other functions, even that won't be possible.
The point is that functions and arrays may be practically different. You can always do an `==` test on the contents of two arrays, but you can't do the same for two arbitrary functions.
> I found this to be a hilariously obtuse and unnecessarily formalist description of a common data structure.
Well it is haskell. Try to understand what a monad is. Haskell loves complexity. That also taps right into the documentation.
> I look at this description and think that it is actually a wonderful definition of the essence of arrays!
I much prefer simplicity. Including in documentation.
I do not think that description is useful.
To me, Arrays are about storing data. But functions can also do that, so I also would not say the description is completely incorrect either.
> who can say that it is not actually a far better piece of documentation than some more prosaic description might have been?
I can say that. The documentation does not seem to be good, in my opinion. Once you reach this conclusion, it is easy to say too. But this is speculative because ... what is a "more prosaic description"? There can be many ways to make a worse documentation too. But, also, better documentation.
> To a language designer, the correspondence between arrays and functions (for it does exist, independent of whether you think it is a useful way to document them) is alluring, for one of the best ways to improve a language is to make it smaller.
I agree that there is a correspondence. I disagree that Haskell's documentation is good here.
> currying/uncurrying is equivalent to unflattening/flattening an array
So, there are some similarities between arrays and functions. I do not think this means that both are equivalent to one another.
> would like to see what it would be like for a language to fully exploit the array-function correspondence.
Does Haskell succeed in explaining what a Monad is? If not, then it failed there. What if it also fails in other areas with regards to documentation?
I think you need to compare Haskell to other languages, C or Python. I don't know if C does a better job with regards to its documentation; or C++. But I think Python does a better job than the other languages. So that is a comparison that should work.
When I'm writing code and need to reach for an array-like data structure, the conceptual correspondence to a function is not even remotely on my radar. I'm considering algorithmic complexity of reads vs writes, managed vs unmanaged collections, allocation, etc.
I guess this is one of those things that's of primary interest to language designers?
https://en.wikipedia.org/wiki/Memoization
If you know that Arrays are Functions or equivalently Functions are Arrays, in some sense, then Memoization is obvious. "Oh, yeah, of course" we should just store the answers not recompute them.
This goes both ways, as modern CPUs get faster at arithmetic and yet storage speed doesn't keep up, sometimes rather than use a pre-computed table and eat precious clock cycles waiting for the memory fetch we should just recompute the answer each time we need it.
I don't think it does.
In fact I don't see (edit: the logcial progression from one idea to the other) at all. Memorization is the natural conclusion of the thought process that begins with the disk/CPU trade off and the idea that "some things are expensive to compute but cheap to store", aka caching.
Whether storing (arrays) or computing (functions) is faster is a quirk of your hardware and use case.
I would also reject the idea that "Arrays are Functions" is equivalent to "Functions are Arrays". They're both true in a sense, but they're not the same statement.
If you actually read the article you'll see that the type of arrays and functions they're talking about are not necessarily the types you'll find in your typical programming language (with some exceptions, as others noted) but more in the area of pure math.
The insights one can gain from this pure math definition are still very much useful for real world programming tough (e.g. memoization), you just have to be careful about the slightly different definitions/implementations.
Needless.
> The insights one can gain from this pure math definition are still very much useful for real world programming tough (e.g. memoization), you just have to be careful about the slightly different definitions/implementations.
I agree completely here. But I think that undermines some of the earlier claims. The math definition only serves as inspiration, we're not using the math definition when we memoize. And the important part you need for that inspiration is a lot narrower than full equivalence.
The blogpost is discussing exactly what you gain (and lose) when arrays and functions fit this strict definition, allowing a unification of the syntax and possible compiler optimizations. I think the point they're making is exactly that having only a loose equivalence between arrays and functions might be a programming status quo that could be holding us back from a higher level abstraction.
Maybe. I'll sit here ready for those insightful uses of stricter connections.
But the memoization example is very loose, and memoization is what I was replying to.
Similar conclusion for using a graph DB for something you'd typically do in a relational DB. Just because you can doesn't mean you should.
And all three are tuple [input, output] pattern matches, with the special case that in “call/select tuples”, input is always fully defined, with output simply being the consequence of its match.
And with arrays, structures and overloaded functions being unions of tuples to match to. And structure inputs (I.e. fields) being literal inline enumeration values.
And so are generics.
In fact, in functional programming, everything is a pattern match if you consider even enumeration values as a set of comparison functions that return the highly used enumerations true or false, given sibling values.
Similarly in Lisp, (a list-oriented language) both functions and arrays are lists.
This article however is discussing Haskel, a Functional Language, which means they are both functions.
In which Lisp? Try this in Common Lisp and it won't work too well:
(let ((array (make-array 20)))
(car array))
What is the car of an array? An array in Lisp (since Lisp 1.5 at least, I haven't read earlier documentation) is an array, and not a list. It does not behave as a list in that you cannot construct it with cons, and you cannot deconstruct it with car or cdr.Yes, I know most people consider it to be a functional language, and some variants like 'common lisp' make that more explicit, but the original concept was markedly different.
TFA does allude to this. An "array indexing function" that just met the API could be implemented as an if-else chain, and would not be satisfactory.
> Haskell provides indexable arrays, which may be thought of as functions whose domains are isomorphic to contiguous subsets of the integers.
with
> Haskell provides indexable arrays, which are functions on the domain [0, ..., k-1]?
Or is the domain actually anything "isomorphic to contiguous subsets of the integers"?
[1] https://hackage.haskell.org/package/array-0.5.8.0/docs/Data-...
(map v [4 5 7])
Would return you a list of the items at index 4, 5, and 7 in the vector v.It also works in pure lambda calculus (assuming you define a vector type). But in lambda calculus, literally every value is a function.
Is an array a function? From one perspective, the array satisfies the abstract requirements we use to define the word "function." From the other perspective, arrays (contiguous memory) exist and are real things, and functions (programs) exist and are something else.
But this whole thing is uninteresting because it is ultimately just a disagreement about definitions.
The matrix multiplication of vectors - or a row and a column vector - which is then just taking the dot product is called an inner product. So for functions the inner product is an integral over where the functions are defined -
< f, g> = \int f(x) g(x) dx
Likewise you can multiply functions by a "kernel" which is a bit like multiplying a vector by a matrix
< A f, g> = \int \int A(x,y) f(y) g(x) dx dy
The fourier transform is a particular kernel
Should you?
This is where I'd be more careful. Maybe it makes sense to some of the langs in TFA. But it reminds me of [named]tuples in Python, which are iterable, but when used as tuples, in particular, as heterogeneous arrays¹ to support returning multiple values or a quick and dirty product type (/struct), the ability to iterate is just a problem. Doing so is almost always a bug, because iteration through a such tuple is nigh always nonsensical.
So, can an array also be f(index) -> T? Sure. But does that make sense in enough context, or does it promote more bugs and less clear code is what I'd be thinking hard about before I implemented such a thing.
¹sometimes tuples are used as an immutable homogeneous array, and that case is different; iteration is clearly sane, then
This is cleverness over craftsmanship. Keeping data and execution as separate as possible is what leads to simplicity and modularity.
The exception is data structures which need the data and the functions that deal with it to expose that data conveniently to be closely tied to each other.
Everything else is an unnecessary dependency that obscures what is actually happening and makes two things that could be separated depend on each other.
This presumes the framework in which one is working. The type of map is and always will be the same as the type of function. This is a simple fact of type theory, so it is worthwhile to ponder the value of providing a language mechanism to coerce one into another.
> This is cleverness over craftsmanship. Keeping data and execution as separate as possible is what leads to simplicity and modularity.
No, this is research and experimentation. Why are you so negative about someone’s thoughtful blog post about the implications of formal type theory?
One doesn't have to presume anything, there are general principles that people eventually find are true after plenty of experience.
The type of map is and always will be the same as the type of function. This is a simple fact of type theory, so it is worthwhile to ponder the value of providing a language mechanism to coerce one into another.
It isn't worthwhile to ponder because this doesn't contradict or even confront what I'm saying.
No, this is research and experimentation.
It might be personal research, but people have been programming for decades and this stuff has been tried over and over. There is a constant cycle where someone thinks of mixing and conflating concepts together, eventually gets burned by it and goes back to something simple and straightforward. What are you saying 'no' to here? You didn't address what I said.
You're mentioning things that you expect to be self evident, but I don't see an explanation of why this simplifies programs at all.
I guess I just disagree with you here. Plenty of programmers with decades of experience have found no such general principle. There is a time and place for everything and dogmatic notions about "never conflate X and Y" because they're "fundamentally different" will always fall flat due to the lack of proof that they are in fact fundamentally different. It depends on the framework in which you're analyzing it.
> It isn't worthwhile to ponder because this doesn't contradict or even confront what I'm saying.
This is a non sequitur. What is worthwhile to ponder has no bearing on what you say. How arrogant can one person be?
> It might be personal research, but people have been programming for decades and this stuff has been tried over and over.
Decades? You think that decades is long enough to get down to the fundamentals of a domain? People have been doing physics for 3 centuries and they're still discovering more. People have been doing mathematics for 3 millennia and they're still discovering more. Let the cycle happen. Don't discourage it. What's it you?
> You're mentioning things that you expect to be self evident, but I don't see an explanation of why this simplifies programs at all.
It may not simplify programs, but it allows for other avenues of formal verification and proof of correctness.
----
Do you have other examples of where concepts were conflated that ended up "burning" the programmer?
Ponder all you want, but what you said wasn't a reply to what I said.
Decades? You think that decades is long enough to get down to the fundamentals of a domain?
It is enough for this because people have been going around in circles constantly the entire time. It isn't the same people, it is new people coming in, thinking up something 'clever' like conflating execution and data, then eventually getting burned by it when it all turns into a quagmire. Some people never realize why their projects turned into a mess that can't move forward quickly without breaking or can't be learned without huge effort of edge cases.
It depends on the framework in which you're analyzing it.
No it doesn't. There are a bunch of fundamentals that are already universal that apply.
First is edge cases. If you make something like an array start acting like a function, you are creating an edge case where the same thing acts differently depending on context. That context is complexity and a dependency you have to remember. This increases the mental load you need to get something correct.
Second is dependencies. Instead of two separate things you now have two things that can't work right because they depend on each other. This increases complexity and mental load while decreasing modularity.
Third is that execution is always more complicated than data. Now instead of something simple like data (which is simple because it is static and self evident) you have it mixed with something complicated that can't be observed unless it runs and all of the states at each line or fragment are observed. Execution is largely a black box, data is clear. Mixing them makes the data opaque again.
You obviously can't treat an impure function as an array and no one would ever claim that. The blog itself isn't claiming that either given that the author is commenting on a nugget from Haskell documentation, and the author is explaining design choices in his own pure functional language.
Your three points only make sense if you're definition of "function" allows side effects. If we're talking about pure functions, then due to referential transparency, arrays are in fact equivalent to functions from contiguous subsets of the integers to another type, as the Haskell documentation indicates.
No I'm not, this applies to both in different ways.
If we're talking about pure functions, then due to referential transparency, arrays are in fact equivalent to functions
Never ever. You're talking about a function that generates data from an index, which is trivial to make. Just because it is possible to disguise it as an array in haskell, C++ or anything else doesn't mean it is a good idea. An array will have vastly different properties fundamentally and can be examined at any time because the data already exists.
Confusing the two is again conflating two things for no reason. Making a function that takes an index and returns something is a trivial interface, there is no value in trying to mix up the two.
Evidence of this can be found in the fact that you haven't tried to explain why this is a good idea, only that it works under haskell's semantics. Being clever with semantics is always possible, that doesn't mean conflating two things and hiding how things actually work is a good idea.
The good idea surrounding this isn't about treating functions as data, but maintaining the purity of the type system allowing the implications of the type system to run their course. You seem to be a very pragmatic programmer and so the way Haskell builds up its own universe in terms of its own type system and Hask (the pseudo-category of Haskell objects) probably seems pointless to you. I can't say for certain, though.
I completely reject most of your claims because they appear to be incoherent within the framework of type theory and functional programming. It looks like you're using reasoning that applies only to procedural programming to attempt to prove why an idea in functional programming is bad.
Haskell is 36 years old. It isn't research and experimentation any more, it is ideas that everyone with experience has had a look at. Some people might be learning haskell for the first time, but that doesn't mean it's still research, that all happened decades ago.
The good idea surrounding this isn't about treating functions as data, but maintaining the purity of the type system allowing the implications of the type system to run their course.
And what are the benefits here? How do they help the things I talked about? How do they avoid the pitfalls?
Also, claiming that the lack of an argument is evidence to the contrary is argument from silence, a logical fallacy.
Not really, because if you could contradict what I've said you would have.
within the framework of type theory and functional programming.
People can gather in a room and tell each other how great they are and how they have all the answers, but in 36 years there is a single email filtering program that was made with haskell.
you're using reasoning that applies only to procedural programming to attempt to prove why an idea in functional programming is bad.
I explained why this is a bad idea from fundamental and universally agreed upon ideas about the best ways to write software.
Functional programing languages had lots of ideas and features that turned out to work well. That doesn't mean that conflating two completely separate concepts is a good idea, no matter what 'type theory' someone comes up with to support it.
Saying something is good because haskell says it is good is circular religious thinking. These two things aren't the same, they are literally two different things and trying to unify them doesn't make life easier for anyone. It's just programming cleverness, like the 'goes to operator --> '
const list = ['a', 'b', 'c']
is syntactic sugar for expressing something like this: function list(index) {
switch (index) {
case 0: return 'a'
case 1: return 'b'
case 2: return 'c'
}
}They are computationally equivalent in the sense that they produce the same result given the same input, but they do not perform the exact same computation under the hood (the array is not syntactic sugar for the function).
For the distinction there, consider the two conventional forms of Fibonacci. Naive recursive (computationally expensive) and linear (computationally cheap). They perform the same computation (given sufficient memory and time), but they do not perform it in the same way. The array doesn't "desugar" into the function you wrote, but they are equivalent in that (setting aside call syntax versus indexing syntax) you could substitute the array for the function, and vice versa, and get the same result in the end.
Advanced version (which defines the ADT we use today): https://en.wikipedia.org/wiki/Mogensen%E2%80%93Scott_encodin...
In fact, from wikipedia:
```
In mathematics, a tuple is a finite sequence or ordered list of numbers or, more generally, mathematical objects, which are called the elements of the tuple. An n-tuple is a tuple of n elements, where n is a non-negative integer. There is only one 0-tuple, called the empty tuple. A 1-tuple and a 2-tuple are commonly called a singleton and an ordered pair, respectively. The term "infinite tuple" is occasionally used for "infinite sequences".
Tuples are usually written by listing the elements within parentheses "( )" and separated by commas; for example, (2, 7, 4, 1, 7) denotes a 5-tuple. Other types of brackets are sometimes used, although they may have a different meaning.[a]
An n-tuple can be formally defined as the image of a function that has the set of the n first natural numbers as its domain. Tuples may be also defined from ordered pairs by a recurrence starting from an ordered pair; indeed, an n-tuple can be identified with the ordered pair of its (n − 1) first elements and its nth element
```
(https://en.wikipedia.org/wiki/Tuple)
From a data structure standpoint, a tuple can be seen as an array of fixed arity/size, then if an array is not a function, so shouldn't a tuple too.
Not even in a functional sense because, even though functions are input-output maps we define, the inputs are dimensionally rich, it's nowhere close to equivalent to jerry rig a contiguous input space for that purpose.
Well, that makes arrays a subset of functions. What is still a "yes" to the questions "are arrays functions?"
And yeah, of course the article names Haskell on its second phrase.
That's an implementation detail, though.
"...and if my Grandmother had wheels she would have been a bike."
let v = myObject [ myFunk ];
Like with arrays-as-functions, the domain of the object-as-function would be its existing keys. Or we could say the domain is any possible value, with the assumption that value of keys which are not stored in the object is always null.Whether new keys could be added at runtime or not is a sepearate question.
It should be easy to extend the syntax so that
myObject (anything) === myObject [anything]
whether myObject is an object, or a 'function' defined with traditional syntax. type AnObject = {
[key: any]: any
}
type FunkyObject = (key: any) => Maybe<any>
Then we can see arrays as a limited type of object/function that only accepts a number (index) as key. type FunkyList = (index: number) => Maybe<any>
We can even consider any variable as a function whose key is the name. type FunkyVariable = (name: string) => Maybe<any>
And any operation as a function whose key is the operator, with arguments, and the return value is the result. type FunkyOperator = (name: string, ...values: any) => any
FunkyOperator('+', 1, 2) // => 3
Even an `if` statement can be a function, as long as the values are functions that return values instead of the values themselves, to allow for "short-circuiting" (latter values are unevaluated if an earlier value is true).So we approach some kind of functional language land where all data structures are modeled using functions.
In Smalltalk constructs like
b ifTrue: [ ... ]
mean that the boolean value 'b' has its method (-function) ifTrue: called with the argument [...] which is a "closure" meaning it is a free-standing function (as opposed to bound functions which are the methods).There are similarly library methods in class Boolean for whileTrue: etc. Functions all the way.
What would be the point of implementing conditional statements as methods/functions? It makes it posssible to extend the set of conditional statements to for instance 3-valued logic by adding a method #ifTrue:ifFalse:ifUnknown: . And of course it makes the syntax of the language very simple.
I wonder how languages support this question of "unevaluated arguments", like in the `if` conditional function. I guess in Lisp(s) they're simply quoted expressions, and in the above Smalltalk example, it sounds like the syntax [...] calls a function with lazily evaluated arguments.
The language in the OP is a special-purpose language for data parallelism, targeting GPUs, and explicitly described as “not intended to replace existing general-purpose languages” (quote from the language’s home page.) As such, it has requirements and constraints that most languages don’t have. Looking at its design through a general-purpose languages lens doesn’t necessarily make sense.
1. The equivalence being discussed is not supported in "every mainstream language" in practice. If you disagree, read https://news.ycombinator.com/item?id=46699933 for a good overview of the equivalence in question and explain how you think mainstream languages support that.
2. The current discussion is in the context of a language targeting CUDA. Currently, very few languages aside from C++ have good CUDA support, and C++ certainly doesn't achieve that by having its arrays be equivalent to functions "in practice" or in any other sense.
Just as an example of what OP is addressing, FTA:
> "To allow for efficient defunctionalisation, Futhark imposes restrictions on how functions can be used; for example banning returning them from branches. These restrictions are not (and ought not be!) imposed on arrays, and so unification is not possible. Also, in Futhark an array type such as [n]f64 explicitly indicates its size (and consequently the valid indices), which can even be extracted at run time. This is not possible with functions, and making it possible requires us to move further towards dependent types - which may of course be a good idea anyway."
As such, it seems to me your comments about this are wildly off the mark.
CUDA happens to be (loosely) source-compatible with C++, but I'm not sure that's the same as saying C++ has good CUDA support. The majority of C++ code does not compile to CUDA (although the inverse is often true).
> C++ certainly doesn't achieve that by having its arrays be equivalent to functions "in practice" or in any other sense
The syntax may not be unified, but what else do you think iterators are for? They are an abstraction to let us ignore pesky details like the underlying storage of arrays, and instead treat them like any other generator function. This is perhaps more evident in a language like Python where generators and iterators are entirely interchangeable.
> in Futhark an array type such as [n]f64 explicitly indicates its size (and consequently the valid indices), which can even be extracted at run time. This is not possible with functions
These are specific oddities of Furthark - we have languages (i.e. C/C++) where the size of an array is not knowable, and we have languages where the range of inputs to a function are knowable (at least for numeric inputs, i.e. Ada)
> Futhark imposes restrictions on how functions can be used; for example banning returning them from branches. These restrictions are not (and ought not be!) imposed on arrays
Again, this is a case of Furthark's own design decisions restricting it. This is only a problem because their arrays are carrying around runtime size information - if they didn't have that, one wouldn't be able to usefully return them from branches anyway. Alternately, there are plenty of ML-family languages where you can return a function from a branch.
I don't get it. How is that not trivial with something like
array·slice(from: initial, to: juncture)
Which is not much different from a·/(i,j) when one want to play the monograph game instead. It can be further reduced to a/(i,j) taking from granted that "/" is given special treatment so it can be employed as part of identifiers.I highly recommend reading: https://dl.acm.org/doi/10.1145/358896.358899
One thing that helped me tremendously with k (and then APL) was when I noticed the morphism xfy<=>f[x;y]<=>(f x y).
This wasn't a new idea; it's right there in:
https://web.archive.org/web/20060211020233/http://community....
starting almost the first page (section 1.2). I simply had not considered the fullness of this because a lot of lispers prefer S-expressions to M-expressions (i.e. that there's more study of it), largely (I conjecture) because S-expressions preserve the morphism between code and data better, and that turns out to be really useful as well.
But the APL community has explored other morphisms beyond this one, and Whitney's projections and views solve a tremendous amount of the problems that macros solve, so I promise I'm not bothered having macros slightly more awkward to write. I write less macros because they're just less useful when you have a better language.
x+y is several step away from plus(x,y), one possible path to render this would be:
x+y
x + y
+ x y
+ x , y
+ ( x , y )
+ ( x , y )
+(x,y)
plus(x,y)
And there are plenty of other options. For example considering method call noted through middot notation: x·+(y)
x·plus(y)
x·plus y
augend·plus(addend)
augend·plus addend
And of course the tersest would be to allow user to define which operation is done on letter agglutination, so `xy` can be x×y or x+y depending on context. The closest things I aware being used in an actual programming language is terms like `2x` in Julia interpreted as "two times x". But I don’t think it allows to configure the tacit operation through agglutination, while it’s allowing to switch first index to be 0 or 1 which is really in the same spirit of configurability over convention.That is how obvious things work. If you were not surprised that a[i:j] and :[a;i;j] are the same (: a i j) then it is because it was obvious to you, and now that you have had it pointed it out to you, you were able to show all of the different other variants of this thing without even being prompted to, so I think you understand the answer to your question now.
def slice(array, start, end):
def new_array(index):
return array(index - start)
return new_array
Or, more elegantly, if you had some sort of infix composition operator (say @, by analogy to matrix multiplication) you would slice an array inline via sliced_array = array @ lambda x: x - start
I think what this really clarifies is that it's quite important that arrays expose their lengths, which there isn't one clear way to do if arrays and functions are interchanged freely.When I was learning programming, I was surprised that in most programming languages we write f(k), but vec[k].
However, we do have syntax vec.at(k) or vec.get(k) in quite a few languages.
Composing injective functions is like applying a permutation array to another.
Yes. And a sandwich is "a stack-based heterogeneous data structure with edible semantics." This is not insight. It is taxonomy cosplay.
Look, arrays and functions share some mathematical structure! - Irrelevant. We do not unify them because representation matters.
When a language makes arrays "feel like functions," what it usually means is: "You no longer know when something is cheap." That is not abstraction. That is obscurity.
Industry programmers do not struggle because arrays lack ontological clarity. They struggle because memory hierarchies exist, cache lines exist, branch predictors exist, GPUs exist, deadlines exist.
> the correspondence between arrays and functions [...] is alluring, for one of the best ways to improve a language is to make it smaller
No. The best way to improve a language is to make it faster, simpler to reason about, and less painful to debug.
> I imagine a language that allows shared abstractions that work for both arrays and appropriate functions
What if we invented increasingly abstract our own words so we don’t have to say ‘for loop’, map, SIMD, kernels?
Making arrays pretend to be functions achieves exactly none of those things. It achieves conference papers that end with “future work”.
Why is this academic slop keep happening ? - Professors are rewarded for novel perspectives, not usable ones.
Functions are first class objects. Funsors generalize the tensor interface to also cover arbitrary functions of multiple variables ("dims"), where variables may be integers, real numbers or themselves tensors. Function evaluation / substitution is the basic operation, generalizing tensor indexing. This allows probability distributions to be first-class Funsors and make use of existing tensor machinery, for example we can generalize tensor contraction to computing analytic integrals in conjugate probabilistic models.
More in the paper: https://arxiv.org/abs/1910.10775Real signature of an array implementation would be something like V: [0, N] -> T, but that implies you need to statically prove that each index i for V[i] is less than N. So your code would be littered with such guards for dynamic indexing. Also, N itself will be dynamic, so you need some (at least limited) dependent typing on top of this.
So you don't want these things in your language so you just accept the domain as some integer type, so now you don't really have V: ℕ -> T, since for i > N there is no value. You could choose V: ℕ -> Maybe<T> and have even cases where i is provably less than N to be littered with guards, so this cure is worse than the disease. Same if you choose V: ℕ -> Result<T, IndexOutOfBounds>. So instead you panic or throw, now you have an effect which isn't really modeled well as a function (until we start calling the code after it a continuation and modify the signature, and...).
So it looks like a function if you squint or are overly formal with guards or effects, but the arrays of most languages aren't that.
> for one of the best ways to improve a language is to make it smaller.
I think this isn't one of those cases.
You just need a subrange type for i. Even PASCAL had those. And if you have full dependent types you can statically prove that your array accesses are sound without required bounds checking. (You can of course use optional bounds checking as one of many methods of proof.)
Arrays are the effect choice in most languages. The signature as a function becomes a gnarly continuation passing if you insist on the equivalence and so most people just tend to think of it imperatively.
instance pi n. Representable (Vec n) where
type Rep (Vec n) = Fin n
index :: Vec n a -> (Fin n -> a)
index = ..
tabulate :: (Fin n -> a) -> Vec n a
tabulate = ..
[1] https://hackage-content.haskell.org/package/adjunctions-4.4....Take the command `ls` for example, it could be expressed as either:
ls -la *.png # lazy
ls(-la, *.png); # formal
For pipes, they could be expressed as either: ls -la *.png | grep cat # lazy
ls(-la, *.png) | grep(cat)
|(ls(-la, *.png), grep(cat)); # formal
I thought about writing this with something like Micropython that could have a very small memory footprint.
SanjayMehta•2w ago
nomel•2w ago
winwang•2w ago
Even non-functional relations can be turned into functions (domain has to change). Like a circle, which is not a function of the x-axis, can be parameterized by an angle theta (... `0 <= theta < 2pi`)
whateveracct•2w ago
The answer is pretty much, yes, everything can be a function. e.g. A KV Map can be a function "K -> Maybe V"
P.S. If this style of thinking appeals to you, go read Algebra Driven Design! https://leanpub.com/algebra-driven-design
Zambyte•2w ago
SanjayMehta•2w ago
An fexpr is a function.
Zambyte•2w ago
> An fexpr is a function.
Try to implement a short circuiting logical `and` operator as a function :-)
jxf•2w ago
IsTom•2w ago
magicalhippo•2w ago
I mean trivially you could say it's a function from (entire machine state) to (entire machine state), but typically we ignore the trivial solution because it's not interesting.
[1]: https://alex.dzyoba.com/blog/os-interrupts/
catapart•2w ago
it's a bit like saying "everything is a process", or "everything is a part of the same singular process that has been playing out since observable history". there's some interesting uses you can get out of that framing, but it's not generally applicable in the way that something like "all programs are unable to tell when a process will halt" is.
but if you really want to harvest the downvotes, I haven't found a better lure than "everything, including the foundations of mathematics, is just a story we are telling each other/ourselves." I know it's true and I still hate it, myself. really feels like it's not true. but, obviously, that's just the english major's version of "everything is a function".
retsibsi•2w ago
IsTom•2w ago
jldugger•2w ago
viraptor•2w ago
SanjayMehta•2w ago