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

Reply via email to