On 22/03/2015, at 5:16 AM, Ryan wrote: > Complicated but interesting problem. I never quite realized how exactly Felix > gets rid of lvalues until now, and I like it.
Actually this is the model. The actual compiler terms still use assignment :) One reason for this is that for compact linear types, there is no pointer. Which is a serious pain. For example the type 2 * 3 // read as bool * sign: bool has 2 cases, sign has 3 (-ve, 0, +ve) as an ordinary tuple would use two * 32bit words (on x86_64). But as a compact linear type: int = 3 * x + y // only uses values 0 upto 5 it only uses 1. C has compact linear types, namely bit fields, but they only handle types which are powers of 2. The problem is the same: they're not addressable. But you can still do an assignment with a get, modify, store. There's another reason to use assignment internally: if you use pointers you have an aliasing issue which in general requires dataflow analysis to solve, which is O(N^3) or worse algo. But with variables, it's easier, no flow is needed to know which store is modified. Conceptually, however, it's simple: pointers represent objects, everything else is a value (and pointers themselves are values). Which leads directly to the problem, how to specify an abstract type is pointer like, that is, it represents a mutable data structure. In C++ you'd use const to convert a pointer (mutable object) into a value (immutable object) but this obviously cannot work because of aliasing. You have to throw in "restrict" to provide a function local solution. In a real language like ATS you'd use linear types. C++ has rvalue binders now which provide some of that functionality. > > As for getters/setters, I can agree they're not the best. They're intractable, that is, completely out of the question. if you have an array of arrays, and you want to modify an element of the inner array, with get/set you'd have to do this: var inner = get (a,i); set (&inner, j, v); set (&a,i,inner); which requires COPYING the whole inner array twice. For an array of some size this is an unacceptable performance problem. So get/set is ruled out. It's not acceptable. But at least in this case it works. Note, writing a 2 argument get/set is NOT allowed, because that defeats combinatorial composition. You have to be able to create new data functors (generic data types) by composition. however the performance issue isn't a theoretical blocker, just a pragmatic one. Using get/set doesn't even work in theory: just consider an uncopyable data structure for example a mutable tree. Or, a doubly linked list. You can't use copying because there are pointers into the data structure elsewhere. But get/ref works, because it ref gives you a pointer into the data structure. (In fact you don't need get, if you have ref, since get is just the deref of a ref). > Whenever I write C++ code, I just make all the attributes I'll likely need to > access public. That has the issue that getters and setters try to resolve > where you can no longer change some implementation details because you'll > break everything. This can't arise if the data type is a product. If the type is a product WITH AN INVARIANT then you need to hide the product to enforce the invariant. In this case, getting and setting fields is nonsense anyhow: if there's an invariant the data type IS NOT a product. Rational numbers, for example, are not products of integers (numerator, denominator) because the numerator can't be 0. Furthermore, for performance, one usually applies the invariant that the denominator must be positive, and the two integers must be relatively prime. That makes the representation unique so comparisons are O(1) (two rationals are equal if and only if their representation is equal). But with rationals, the very idea of get/set the numerator or denominator is pure rubbish, because they're not well defined. > > Python and Objective C solve this with properties. Which I like. > > But Felix doesn't have this problem since `x.y` is a function call anyway. > Cool. Yeah. For a product, the attribute is a projection. However you can have non-projection functions. Also, there can be more than one "basis" for a product type, that is, more than one pair of projections may exist. For example a well known one: for a pair of integers, instead of first, second you can use the pair of functions first + second, first - second which is, in fact, how colour analogue television works (RGB is actually represented as black and white and two difference signals, which makes it easy for a B/W TV set to decode the signal). Another "almost" example is complex numbers (cartesian and polar representations). It is important to understand that for a product, the chosen canonical projections ARE the representation. There's no such thing as data. The names of "member variables" in a class are FUNCTIONS. Even in C++. Fields are just projection functions. Lvalues just provide a very bad way to model what Felix finally does correctly. More generally, given isomorphic representations of products, one might consider an abstraction, with multiple functions. For example for complex you provide get_x, get_y, get_r, get_theta as well as anything else. Then you provide axioms which tell you which combinations form a basis. This problem, generalised, is a serious issue with type classes. Consider a total order: == < the problem is we want also != <= >= > In fact, some times < is fundamental and sometimes <=. So when we instantiate a total order the requirement SHOULD be to provide a basis of definitions, and the derived definitions should be generated. But what Felix (and Haskell) ACTUALLY do is force the basis. In Felix virtual fun < ... // axiom virtual fun == ... // axiom fun > (x,y) => y < x // derived Really ALL the functions should be virtual, with defaults, so that if you provide enough functions the others get defined (by the defaults). If you provide too many, then that's OK provided they obey the axiomatic relations. You might do this for performance. And if you don't provide enough, the compiler barks at you. I have no idea how to do this. Neither do the Haskell people. You can't even provide defaults, because what works for a derived function depends on which other functions are specified by the user. In case you're wondering the relevance: OO is utterly WRONG by thinking just because you provide functions, you have an abstraction and avoid representation details. First, fields (data representation) are ALREADY functions (projections). And secondly, even if you hide that the set of access methods you provide are STILL a concrete representation. What hiding does is hide which functions are axioms and which are derived, they all become properties (lemmas) which makes the axiomatisation arbitrary. So it is "more abstract" than before, but that's the end of it. And the real crux of the matter arises when you have a mutable data type, because you cannot easily set a new value in pieces. You cannot just "set" the numerator, then the denominator, of a rational number. -- john skaller skal...@users.sourceforge.net http://felix-lang.org ------------------------------------------------------------------------------ Dive into the World of Parallel Programming The Go Parallel Website, sponsored by Intel and developed in partnership with Slashdot Media, is your hub for all things parallel software development, from weekly thought leadership blogs to news, videos, case studies, tutorials and more. Take a look and join the conversation now. http://goparallel.sourceforge.net/ _______________________________________________ Felix-language mailing list Felix-language@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/felix-language