Discussion:
A couple of questions about goops method parameters
(too old to reply)
Carlos Pita
2014-09-03 02:05:02 UTC
Permalink
Hi all,

I've some questions about parameter handling in goops methods:

1) initialize takes the initargs argument as a list, because of the
way make-instance is defined. But the documentation states:

In theory, initarg … can have any structure that is understood by
whatever methods get applied when the initialize generic function is
applied to the newly allocated instance.

One could think this implies that initargs won't necessarily be packed
as a list. Indeed, that would be very convenient and sensible for
custom initialization methods. But make-instance is implemented as:

(define-method (make-instance (class <class>) . initargs)
(let ((instance (allocate-instance class initargs)))
(initialize instance initargs)
instance))

So one would need to rewrite it in order to apply initargs, or
something like that, in order to "unpack" the list.

How do you typically implement a custom initialization method, then?
Using pattern matching? Maybe ice-9 optargs? Maybe apply? Maybe you
directly call initialize? In any case, why is this so? Wouldn't it be
better for initialize to just get the "unpacked" argument list? This
perplexes me.

2) What is the relationship between the lambda* family and methods?
Are methods restricted in the sense that they can't aspire to get the
greater flexibility of lambda* parameter handling? Maybe because of
the way dispatching is done?

Thank you very much in advance for any help.

Best regards
--
Carlos
Carlos Pita
2014-09-03 15:49:38 UTC
Permalink
Post by Carlos Pita
directly call initialize? In any case, why is this so? Wouldn't it be
better for initialize to just get the "unpacked" argument list? This
perplexes me.
I've been thinking about this, and lurking at the tinyclos
implementation, and then reading the cltl sections about initialization
in clos. Despite the fact that clos make-instance indeed *apply* the
initargs when calling initialize-instance, thus unpacking/destructuring
the initargs list, everything in the specification seems to imply a
plist style of usage. Even the name part of a name-value pair is used to
choose between (i) direct initialization of the corresponding slot or
(ii) calling the initialize-instance method with the name-value
pair. So, all in all, the usage implied is akin to dealing with a (name
value name value ...) list argument in goops or tinyclos.

All this seems specifically oriented to slot initialization, not to
general construction of a new instance while preserving some class
invariant imposed to the internal data/slots (please, notice that my
interest here is not about encapsulation or access control matters). So
maybe I'm suffering from a paradigm mismatch kind of thing, as I'm
trying to fit initialize into the usual OOP concept of constructor. From
this perspective initialize (and initialize-instance) looks to me like
erotic lingerie for slots (no pun intended, really :)), as maybe it
isn't completely transparent but it's surely translucent regarding the
underlying slots. The focus seems to be at a lower level of abstraction
than the level the usual constructor operates in. I'm aware of virtual
slots and, of course, of the possibility of implementing a custom
initialize method, but these solutions only buy some degrees of freedom
around the slot initializing focus: virtual slots still looks like slots
and the argument to initialize is still a list, presumably of slot
name-value pairs. I'm also aware of the possibility of defining read
only slots but, again, this is not generally enough to offer convenient
ways of construction that preserve some desired invariant.

So, a question to the experienced lispers here, a question that's not
specifically guile or goops or scheme related. Is the make (or
make-instance) way of constructing a new instance usually exposed to the
final user? Or a factory function, operating at a higher level of
abstraction, is intended to wrap the lower level, slot-fillig oriented,
call to make? In this case, a custom initialize method implementation
should be seen more as a complement to make than as a proper
constructor/factory.

(Moreover, the visibility and access of constructor/factory functions
are easily controlled using the module system although, as I've said
before, that's not my interest here).

Best regards
--
Carlos
Marko Rauhamaa
2014-09-03 16:47:39 UTC
Permalink
Post by Carlos Pita
So, a question to the experienced lispers here, a question that's not
specifically guile or goops or scheme related. Is the make (or
make-instance) way of constructing a new instance usually exposed to
the final user? Or a factory function, operating at a higher level of
abstraction, is intended to wrap the lower level, slot-fillig
oriented, call to make? In this case, a custom initialize method
implementation should be seen more as a complement to make than as a
proper constructor/factory.
I saw the light and left goops behind. I built a simple system:

* Not slot-centric but method-centric.

* No classes, only objects.

IMO, the end result is more schemey than Goops.

It contains:

(make-object parentage . methods)

where

parentage is #f, an object or a list of objects

methods contains procedures, or name-procedure pairs

Example:

(define (<point> .x .y)
(define (x) .x)
(define (y) .y)
(make-object #f x y))

(let ((point (<point> 7 8)))
(point #:y))
=> 8

Marko
Carlos Pita
2014-09-03 18:05:30 UTC
Permalink
http://www.aiai.ed.ac.uk/~jeff/clos-guide.html

and

http://permalink.gmane.org/gmane.lisp.cl-pro/24

support the "make as implementation detail" view.

Some excerpts:

It's often a good idea to define your own constructor functions, rather
than call make-instance directly, because you can hide implementation
details and don't have to use keyword parameters for everything. For
instance, if you wanted the name and age to be required, positional
parameters, rather than keyword parameters, you could define

(defun make-person (name age)
(make-instance 'person :name name :age age))

[Notice how more convolved implementing the same using initialize would be]
-------------

In addition to this, you'd use a factory function, rather than having
the client call make-instance, to hide the CLOS nature of the type.
Post by Marko Rauhamaa
Post by Carlos Pita
So, a question to the experienced lispers here, a question that's not
specifically guile or goops or scheme related. Is the make (or
make-instance) way of constructing a new instance usually exposed to
the final user? Or a factory function, operating at a higher level of
abstraction, is intended to wrap the lower level, slot-fillig
oriented, call to make? In this case, a custom initialize method
implementation should be seen more as a complement to make than as a
proper constructor/factory.
* Not slot-centric but method-centric.
* No classes, only objects.
IMO, the end result is more schemey than Goops.
(make-object parentage . methods)
where
parentage is #f, an object or a list of objects
methods contains procedures, or name-procedure pairs
(define (<point> .x .y)
(define (x) .x)
(define (y) .y)
(make-object #f x y))
(let ((point (<point> 7 8)))
(point #:y))
=> 8
Marko
Panicz Maciej Godek
2014-09-03 16:20:49 UTC
Permalink
Howdy,
Post by Carlos Pita
How do you typically implement a custom initialization method, then?
Using pattern matching? Maybe ice-9 optargs? Maybe apply? Maybe you
directly call initialize? In any case, why is this so? Wouldn't it be
better for initialize to just get the "unpacked" argument list? This
perplexes me.
I'm not sure if I get the question right, but you usually overload the
"initialize" method (which calls (next-method)) and use the
"let-keywords" form if you want to add additional initialization
parameters
Post by Carlos Pita
2) What is the relationship between the lambda* family and methods?
Are methods restricted in the sense that they can't aspire to get the
greater flexibility of lambda* parameter handling? Maybe because of
the way dispatching is done?
This is a good question and I'd like to know the answer myself,
but it seems to me that GOOPS methods and keyword arguments are
simply incompatible. Obviously you can obtain default arguments in GOOPS
quite easily:

(define-method (f (x <t1>) (y <t2>))
...)

(define-method (f (x <t1>))
(f x default-y-value))

(define-method (f)
(f default-x-value))

One could also come up easily with a macro that allows to expand

(define-method/default (f (x <t1> default-x-value) (y <t2> default-y-value))
...)

to the above code. But I don't think that the goops dispatcher was
anyhow suited for the keyword arguments (I could be wrong, though)
Nala Ginrut
2014-09-05 08:32:03 UTC
Permalink
Hi Carlos!
Post by Carlos Pita
2) What is the relationship between the lambda* family and methods?
Are methods restricted in the sense that they can't aspire to get the
greater flexibility of lambda* parameter handling? Maybe because of
the way dispatching is done?
IMO, when you have lambda*, you never need define-method. Actually, I
want to say, once you have such FP features, you don't need OOP anymore.

You can mix them while programming, but you don't have to.
Anyway, if you try to avoid to use GOOPS someday, you have to handle
dispatching by yourself. ;-)
Post by Carlos Pita
Thank you very much in advance for any help.
Best regards
--
Carlos
Carlos Pita
2014-09-05 12:47:50 UTC
Permalink
Hi Nala,
Post by Nala Ginrut
IMO, when you have lambda*, you never need define-method. Actually, I
want to say, once you have such FP features, you don't need OOP anymore.
I really don't see classes and multimethods a la CLOS competing against
FP features. They are about certain ways of composing structures and
providing common functional interfaces to them (and so, about certain
ways of dispatching function calls). They don't promote mutability. They
don't promote packaging data and methods in rigid ways. They just
capture a pattern that would be cumbersome to implement by hand each
time (even with the help of metaprogramming).

I don't care about "everything is an object" stuff, but I do care about
using the right tool for the problem at hand, and it feels good when the
problem is a frequent one and the tool already exists.
Post by Nala Ginrut
You can mix them while programming, but you don't have to. Anyway, if
you try to avoid to use GOOPS someday, you have to handle dispatching
by yourself. ;-)
You see. There could be cases for which goops dispatching were not good
enough, I can't say it for sure. But multimethods are an incredible
flexible mechanism and you still have the mop to tweak, so I will write
my own dispatching code and "meta-code" only as a last LAST resource.

Best regards
--
Carlos
Panicz Maciej Godek
2014-09-05 19:03:08 UTC
Permalink
Post by Nala Ginrut
Hi Carlos!
Post by Carlos Pita
2) What is the relationship between the lambda* family and methods?
Are methods restricted in the sense that they can't aspire to get the
greater flexibility of lambda* parameter handling? Maybe because of
the way dispatching is done?
IMO, when you have lambda*, you never need define-method. Actually, I
want to say, once you have such FP features, you don't need OOP anymore.
So perhaps you could tell me how to design a GUI framework in FP and
without OOP. To me it seems that GUI is the main domain the OOP was
crafted for, but if you have some nice functional ideas, perhaps you
could help me to redesign my framework.

The base of the framework can be browsed here:

https://bitbucket.org/panicz/slayer/src/94c9dde264759cbbd8d4a88d2581b77f55cc0bd6/guile-modules/widgets/base.scm?at=default

What is particularly relevant is the <widget> class. I recently
started creating my own OOP framework atop of GOOPS, so the same class
could be equivalently written as

https://bitbucket.org/panicz/slayer/src/94c9dde264759cbbd8d4a88d2581b77f55cc0bd6/guile-modules/extra/noobs.scm?at=default
(see at the bottom)

But I don't see any good alternative to OOP.
David Thompson
2014-09-05 19:12:53 UTC
Permalink
Post by Panicz Maciej Godek
So perhaps you could tell me how to design a GUI framework in FP and
without OOP. To me it seems that GUI is the main domain the OOP was
crafted for, but if you have some nice functional ideas, perhaps you
could help me to redesign my framework.
Are you familiar with functional reactive programming?

http://elm-lang.org/learn/What-is-FRP.elm

Using FRP, we can model with mutable state in a pure, functional way.
That is, the necessary mutation is hidden behind the runtime of the FRP
implementation.

Just some food for thought.
--
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate
Panicz Maciej Godek
2014-09-05 19:35:44 UTC
Permalink
Post by David Thompson
Post by Panicz Maciej Godek
So perhaps you could tell me how to design a GUI framework in FP and
without OOP. To me it seems that GUI is the main domain the OOP was
crafted for, but if you have some nice functional ideas, perhaps you
could help me to redesign my framework.
Are you familiar with functional reactive programming?
http://elm-lang.org/learn/What-is-FRP.elm
Using FRP, we can model with mutable state in a pure, functional way.
That is, the necessary mutation is hidden behind the runtime of the FRP
implementation.
Just some food for thought.
Sure, I've been reading a lot, but I didn't manage to get much of it.
I mean, the toy examples are really nice, but it's hard for me to see
the advantage of FRP over OOP in practical systems (e.g. windowed
applications with buttons and so on). Although I see value in
encapsulating state mutations, the notion of state seems inevitable in
describing such applications (like the mere fact that a checkbox can
be checked or not -- so it's a state which is a part of the
description).

BTW I recently ran into a problem with your signal propagation
framework from (guile 2d). Namely, if a signal is itself a mutable
object (like a vector or an array), then changing the value of that
object doesn't propagate (because it happens only when you use
slot-set!, and not (vector-set! (slot-ref ...)...)).
I can't find any workaround for that.
David Thompson
2014-09-05 19:55:28 UTC
Permalink
Post by Panicz Maciej Godek
Post by David Thompson
Post by Panicz Maciej Godek
So perhaps you could tell me how to design a GUI framework in FP and
without OOP. To me it seems that GUI is the main domain the OOP was
crafted for, but if you have some nice functional ideas, perhaps you
could help me to redesign my framework.
Are you familiar with functional reactive programming?
http://elm-lang.org/learn/What-is-FRP.elm
Using FRP, we can model with mutable state in a pure, functional way.
That is, the necessary mutation is hidden behind the runtime of the FRP
implementation.
Just some food for thought.
Sure, I've been reading a lot, but I didn't manage to get much of it.
I mean, the toy examples are really nice, but it's hard for me to see
the advantage of FRP over OOP in practical systems (e.g. windowed
applications with buttons and so on). Although I see value in
encapsulating state mutations, the notion of state seems inevitable in
describing such applications (like the mere fact that a checkbox can
be checked or not -- so it's a state which is a part of the
description).
Yes, it's state, and state will always be there in a realtime
application. However, you can still model it in a functional way with
procedures that are idempotent and objects that are immutable. You just
need to glue it all together with something that tracks the current
state of the application.
Post by Panicz Maciej Godek
BTW I recently ran into a problem with your signal propagation
framework from (guile 2d). Namely, if a signal is itself a mutable
object (like a vector or an array), then changing the value of that
object doesn't propagate (because it happens only when you use
slot-set!, and not (vector-set! (slot-ref ...)...)).
I can't find any workaround for that.
I don't consider it a problem because the values stored signals are
intended to be immutable. I will make that clear when I get around to
documenting things better. If you mutate an object within the signal
graph, bad things are bound to happen. The only reasonable side-effects
are those that do not change values stored within signals, like writing
to a log file or playing a sound. I have intentionally kept mutation at
lowest layer of the system, abstracted away from the user.
--
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate
Taylan Ulrich Bayirli/Kammer
2014-09-05 20:10:09 UTC
Permalink
[...] it's hard for me to see the advantage of FRP over OOP in
practical systems (e.g. windowed applications with buttons and so
on). [...]
An off-topic remark:

I don't know about *functional* reactive programming but from my
experience so far as an iOS developer, I've been *longing* for a
reactive programming system that automates state changes even if not
fully hiding them. It would be invaluable being able to say
"button2.leftEdge = button1.rightEdge + 20px" and have this equation be
held automatically on changes to the layout of button1 (which might
happen because it itself reacts to other layout changes), or to be able
to say "button.disabled = condition1 or condition2" and have the
disabled status of button update automatically as the truthiness of the
conditions changes. (The former use-case is actually covered by "layout
constraints", but that's strictly limited to layouting.)

Declarative programming FTW.

Taylan
David Thompson
2014-09-05 20:50:54 UTC
Permalink
Post by Taylan Ulrich Bayirli/Kammer
[...] it's hard for me to see the advantage of FRP over OOP in
practical systems (e.g. windowed applications with buttons and so
on). [...]
I don't know about *functional* reactive programming but from my
experience so far as an iOS developer, I've been *longing* for a
reactive programming system that automates state changes even if not
fully hiding them. It would be invaluable being able to say
"button2.leftEdge = button1.rightEdge + 20px" and have this equation be
held automatically on changes to the layout of button1 (which might
happen because it itself reacts to other layout changes), or to be able
to say "button.disabled = condition1 or condition2" and have the
disabled status of button update automatically as the truthiness of the
conditions changes. (The former use-case is actually covered by "layout
constraints", but that's strictly limited to layouting.)
In my reactive programming system, you could say:

(define-signal button2-left-edge
(signal-map (cut + <> 20) button1-right-edge))

Which is, of course, more verbose than your example. I want to explore
creating a macro that would abstract away the unboxing of signals so I
could simply write...

(define-signal button-2-left-edge
(+ button1-right-edge 20))

... and it would DTRT. Racket's FrTime can do this, which is neat.
Post by Taylan Ulrich Bayirli/Kammer
Declarative programming FTW.
Agreed. :)
--
David Thompson
Web Developer - Free Software Foundation - http://fsf.org
GPG Key: 0FF1D807
Support the FSF: https://fsf.org/donate
Neil Jerram
2014-09-07 10:33:25 UTC
Permalink
Post by Taylan Ulrich Bayirli/Kammer
[...] it's hard for me to see the advantage of FRP over OOP in
practical systems (e.g. windowed applications with buttons and so
on). [...]
I don't know about *functional* reactive programming but from my
experience so far as an iOS developer, I've been *longing* for a
reactive programming system that automates state changes even if not
fully hiding them. It would be invaluable being able to say
"button2.leftEdge = button1.rightEdge + 20px" and have this equation be
held automatically on changes to the layout of button1 (which might
happen because it itself reacts to other layout changes), or to be able
to say "button.disabled = condition1 or condition2" and have the
disabled status of button update automatically as the truthiness of the
conditions changes. (The former use-case is actually covered by "layout
constraints", but that's strictly limited to layouting.)
IIRC, Metafont does that; but obviously it isn't intended as a general
language. Are there more general languages that solve equations like
this?

Regards,
Neil
Taylan Ulrich Bayirli/Kammer
2014-09-07 15:27:48 UTC
Permalink
Post by Neil Jerram
IIRC, Metafont does that; but obviously it isn't intended as a general
language. Are there more general languages that solve equations like
this?
Panicz Maciej Godek
2014-09-05 20:10:26 UTC
Permalink
Post by David Thompson
http://elm-lang.org/learn/What-is-FRP.elm
Using FRP, we can model with mutable state in a pure, functional way.
OTOH, when you take a look at the example code (Mario), you can trace
the notion of objects. E.g.
mario = { x = 0, y = 0, vx = 0, vy = 0, dir = "right" }

What else is that, if not an object?
"Well, it's a structure", one could say -- because it has no methods.
However, this is just what the most rudimentary GOOPS objects are -- a
named tuple (provided that you use no virtual slots). I think that it
is a big problem of Scheme, that it does not have any noncontroversial
and commonly accepted way for creating named tuples.

Furthermore, instead of using explicit side effects, as one would
normally do, the Mario example first defines a step function, and
calls "foldp step mario input". Although I do appreciate efforts like
in "How to Design Worlds" book or "Introduction to Systematic Program
Design" course, to avoid explicit mutation (because as SICP shows, it
complicates the model of computation), I don't see so many benefits of
avoiding mutation in complex realtime systems.

Actually, when I look at the Mario example, I have a feeling that the
code would be much cleaner and easier to follow if it was written in a
more traditional imperative/callback style.
Taylan Ulrich Bayirli/Kammer
2014-09-05 20:18:09 UTC
Permalink
[...] I think that it is a big problem of Scheme, that it does not
have any noncontroversial and commonly accepted way for creating named
tuples.
Does SRFI-9 not count because it creates tuple *types* and doesn't
support immediate creation of tuples of an "anonymous type"? (Could be
an interesting feature, though not sure how useful in the end.) Or do
you just not consider SRFI-9 to be commonly accepted? AFAIK it's pretty
widely implemented, and for the record(!) it's been standardized as a
part of R7RS-small.

Taylan
Panicz Maciej Godek
2014-09-05 20:37:49 UTC
Permalink
2014-09-05 22:18 GMT+02:00 Taylan Ulrich Bayirli/Kammer
Post by Taylan Ulrich Bayirli/Kammer
[...] I think that it is a big problem of Scheme, that it does not
have any noncontroversial and commonly accepted way for creating named
tuples.
Does SRFI-9 not count because it creates tuple *types* and doesn't
support immediate creation of tuples of an "anonymous type"? (Could be
an interesting feature, though not sure how useful in the end.) Or do
you just not consider SRFI-9 to be commonly accepted? AFAIK it's pretty
widely implemented, and for the record(!) it's been standardized as a
part of R7RS-small.
There are a few issues here. The fact that it is impossible to create
anonymous type is one thing. Another is that each record type
introduces accessor bindings to a global namespace. In case of Elm,
one could write
mario = { x = 0, y = 0, dx = 0, dy = 0 }
and then access the fields with the dot notation, i.e. mario.x

There are other representations (like basket list or assoc list) that
avoid that problem, but they generate other ones -- namely, that the
access times get linear, and in case of assoc lists there is a huge
overhead of data, and in case of basket lists one needs to pass around
additional information regarding the names of subsequent fields.

And the sole fact that there are other reasonable represetations leads
to the conclusion that none is commonly accepted.

(Actually I think the nicest solution I've seen was in Erlang, but
unfortunately it wouldn't go well with Scheme)
Marko Rauhamaa
2014-09-05 20:51:02 UTC
Permalink
Post by Panicz Maciej Godek
There are other representations (like basket list or assoc list) that
avoid that problem, but they generate other ones -- namely, that the
access times get linear, and in case of assoc lists there is a huge
overhead of data, and in case of basket lists one needs to pass around
additional information regarding the names of subsequent fields.
Dynamic programming languages lack a true, efficient dot notation.
That's a price I'm willing to pay (especially since Guile allows me to
switch to C where necessary). For example, Python and JavaScript
translate x.f into a hash table lookup.

In my tests, Guile's alists are more efficient than hash tables up to
maybe a hundred elements or so (IIRC). That's why I switched to alists
in my tiny object system.

As for the data overhead, I haven't yet really run into that problem.
Again, high memory use is a common issue with dynamic programming
languages. Python's objects are quite sizable as well.


Marko
Taylan Ulrich Bayirli/Kammer
2014-09-05 21:53:18 UTC
Permalink
Post by Marko Rauhamaa
Dynamic programming languages lack a true, efficient dot notation.
If with a "true, efficient dot notation" you mean for example C structs,
then records fill that role except for using accessor procedures instead
of syntax.

(Under the right conditions, usage of records could compile to direct
O(1) memory access (pointer+offset), just like usage of vectors. Arrays
and structs in C are in direct analogy to vectors and records in Scheme;
the only difference being Scheme's general requirement of type-checks.)

Though after pondering a bit I realized that it indeed seems impossible
to compile "(.bar foo)" (could result from "foo[.bar]" via SRFI-105)
into the correct memory offset, if there are multiple record types each
with a '.bar' field, because it's not statically known which record type
'foo' has. Maybe that's exactly what you meant.

Taylan
Marko Rauhamaa
2014-09-05 22:26:27 UTC
Permalink
Post by Taylan Ulrich Bayirli/Kammer
Though after pondering a bit I realized that it indeed seems impossible
to compile "(.bar foo)" (could result from "foo[.bar]" via SRFI-105)
into the correct memory offset, if there are multiple record types each
with a '.bar' field, because it's not statically known which record type
'foo' has. Maybe that's exactly what you meant.
Yes.

It is amusing, though, that C originally suffered from the same issue:
the struct field offsets were global linker objects. That's why to this
day, unix/linux C structs have ugly field prefixes:

struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};

struct timezone {
int tz_minuteswest;
int tz_dsttime;
};

struct linger {
int l_onoff;
int l_linger;
};

struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};

struct in_addr {
uint32_t s_addr;
};

struct iovec {
void *iov_base;
size_t iov_len;
};

and so on...


Marko
Marko Rauhamaa
2014-09-05 20:44:04 UTC
Permalink
I think that it is a big problem of Scheme, that it does not have any
noncontroversial and commonly accepted way for creating named tuples.
That's what alists are. They may not be the most beautiful way to
represent data as S expressions but they sure are noncontroversial and
commonly accepted.

However, objects, in my opinion, are the antithesis of tuples. Objects
are the focal points of methods. Whether the black box contains data and
in what form is none of the rest of the world's concern.

IMO, GOOPS has two main flaws:

* It brings an object's data slots to the fore while brushing methods
aside. The object constructor syntax is more or less directly tied to
the data slots.

* It introduces a very strong, almost Linnaean, type system to Scheme,
where it seems out of place. I see no principal reason for such
classification. I don't declare my numbers in Scheme; why should I
declare my object types?


Marko
Panicz Maciej Godek
2014-09-05 21:08:43 UTC
Permalink
Post by Marko Rauhamaa
I think that it is a big problem of Scheme, that it does not have any
noncontroversial and commonly accepted way for creating named tuples.
That's what alists are. They may not be the most beautiful way to
represent data as S expressions but they sure are noncontroversial and
commonly accepted.
I think that (putting side other issues) if one person in this thread
claims that srfi-9 is a commonly accepted representation, and another
claims that alists are a a commonly accepted representation, then it's
a proof that none is commonly accepted.
Post by Marko Rauhamaa
However, objects, in my opinion, are the antithesis of tuples. Objects
are the focal points of methods. Whether the black box contains data and
in what form is none of the rest of the world's concern.
Apparently our views on the essence of OOP differ. I perceive an
object as an aggregation of properties, and a set of methods I would
call an interface. Perhaps to you doing OOP is about defining
interfaces, and to me it's more about aggregating properties. I
wouldn't say that anyone of us is more right than the other.
Post by Marko Rauhamaa
* It brings an object's data slots to the fore while brushing methods
aside. The object constructor syntax is more or less directly tied to
the data slots.
I agree that the constructor syntax isn't expressible to handle some
very common use cases, and requires to overload the "initialize"
method in many trivial situations. But my solution to this problem is
to construct OOP framework atop of GOOPS. It might not be the simplest
solution, but I find it promising
Post by Marko Rauhamaa
* It introduces a very strong, almost Linnaean, type system to Scheme,
where it seems out of place. I see no principal reason for such
classification. I don't declare my numbers in Scheme; why should I
declare my object types?
I don't think I understand. There is no strong type system, and
there's no need to declare object types. The types are mainly for
convinience -- to allow you to implement the same interfaces for
different objects.
Marko Rauhamaa
2014-09-05 22:14:06 UTC
Permalink
Post by Panicz Maciej Godek
Post by Marko Rauhamaa
However, objects, in my opinion, are the antithesis of tuples.
Objects are the focal points of methods. Whether the black box
contains data and in what form is none of the rest of the world's
concern.
Apparently our views on the essence of OOP differ. I perceive an
object as an aggregation of properties, and a set of methods I would
call an interface. Perhaps to you doing OOP is about defining
interfaces, and to me it's more about aggregating properties. I
wouldn't say that anyone of us is more right than the other.
There's no point arguing about terminology. All I want to say is that
"my" OOP is desirable to me, "your" OOP is something I want to steer
away from.

I don't need interfaces as first-class entities in an object system.
Ducktyping involves less clutter and is more generic.
Post by Panicz Maciej Godek
Post by Marko Rauhamaa
* [GOOPS] introduces a very strong, almost Linnaean, type system to
Scheme, where it seems out of place. I see no principal reason for
such classification. I don't declare my numbers in Scheme; why
should I declare my object types?
I don't think I understand. There is no strong type system, and
there's no need to declare object types. The types are mainly for
convinience -- to allow you to implement the same interfaces for
different objects.
The types are a great inconvenience, syntactically and conceptually.
Syntactically, your GOOPS method definitions make your Scheme code look
like Pascal with the class names sprinkled among the parameters.
Conceptually, the classes force me to put objects into buckets that
don't correspond to my thought processes. Even Java offers anonymous
classes for the purpose.


Marko
Panicz Maciej Godek
2014-09-06 08:53:58 UTC
Permalink
Post by Marko Rauhamaa
Post by Panicz Maciej Godek
Post by Marko Rauhamaa
* [GOOPS] introduces a very strong, almost Linnaean, type system to
Scheme, where it seems out of place. I see no principal reason for
such classification. I don't declare my numbers in Scheme; why
should I declare my object types?
I don't think I understand. There is no strong type system, and
there's no need to declare object types. The types are mainly for
convinience -- to allow you to implement the same interfaces for
different objects.
The types are a great inconvenience, syntactically and conceptually.
Syntactically, your GOOPS method de, it plainly extends the means of expression (and I think there's no problem in providing one's own syntax with a macro)finitions make your Scheme code look
like Pascal with the class names sprinkled among the parameters.
Conceptually, the classes force me to put objects into buckets that
don't correspond to my thought processes. Even Java offers anonymous
classes for the purpose.
Now I don't understand what you're saying to even bigger extent.
GOOPS does not offer a type system, but a multiple method dispatch
system. It doesn't take away any freedom of using GOOPS objects with
regular Scheme functions, but allows to use one interface for
different implementations. Whether one likes the syntax or not, it
plainly extends the means of expression (and I think there's no
problem in providing one's own syntax with a macro)

However, I'd rather say that the lack of any type system in Guile is
an inconvinience, because static type checking allows to avoid a huge
class of software errors, and a good type system (like the one in
Haskell) actually enhances language's expressiveness. It's an issue
that's been talked over so many times, that it's already present in
comic strips:
http://ro-che.info/ccc/17
Taylan Ulrich Bayirli/Kammer
2014-09-06 10:44:01 UTC
Permalink
Post by Panicz Maciej Godek
However, I'd rather say that the lack of any type system in Guile is
an inconvinience, because static type checking allows to avoid a huge
class of software errors, and a good type system (like the one in
Haskell) actually enhances language's expressiveness. It's an issue
that's been talked over so many times, that it's already present in
http://ro-che.info/ccc/17
I suspect that comic strip comes from someone who mostly witnessed silly
flamewars between Haskellites with a higher-than-thou attitude, and CS
unwary users of languages like JavaScript, Python, Ruby, etc. ;)

In fact, the whole mention of a "battle" between the two groups, and
showing zero overlap between the "proponents" of either strategy, tells
me that the author is seriously misguided themselves. Maybe I'm reading
too much into it, but what's at least obvious is that the author is a
fan of Haskell.

I'm sure that most serious Lispers and other CS-aware dynlang users are
aware of the expressive power of good static type systems, and have
respect for ML and Miranda descendants. However, I don't know of any
hard evidence for the relevancy of the class of bugs prevented by static
typing, given there is otherwise good program design and documentation.
Only recently I met a static typing proponent who was merely spiteful
against the horrible practices of some web developers (JavaScript users)
they worked with...

A good optional static type system could be neat for Guile, but not sure
what priority this should have. (For now I would rather want sealed
modules and the ability to static-import them into another.)

Taylan
Marko Rauhamaa
2014-09-06 11:27:31 UTC
Permalink
Post by Panicz Maciej Godek
However, I'd rather say that the lack of any type system in Guile is
an inconvinience, because static type checking allows to avoid a huge
class of software errors, and a good type system (like the one in
Haskell) actually enhances language's expressiveness.
We already have a satisfactory selection of languages with static type
annotation. The primary upside of static types is much faster code. The
downside is boilerplate and clutter that make it a huge chore to write
and maintain the code. In my experience, high-level programming
languages allow you to accomplish more challenging feats with better
quality and productivity than statically typed languages.

I'm saying use low-level programming languages when you have to and
high-level programming languages when you can.


Marko
Taylan Ulrich Bayirli/Kammer
2014-09-06 11:54:03 UTC
Permalink
Post by Marko Rauhamaa
The primary upside of static types is much faster code.
Optimization of dynamic typing can go pretty far AFAIK. In an ideal
case, type-checks are hoisted to outside critical sections of code and
don't affect the speed of e.g. a tight loop. And then there's JIT and
all that jazz...
Post by Marko Rauhamaa
The downside is boilerplate and clutter that make it a huge chore to
write and maintain the code.
Users of languages with good static type systems like ML and Miranda
descendants would rather argue that types make the program more
maintainable, and aren't too much of a bother since they're inferred in
many common cases. Not my own experience, but I have no reason for a
wholesale disbelief against them (only skepticism on *how* great static
typing is).
Post by Marko Rauhamaa
In my experience, high-level programming languages allow you to
accomplish more challenging feats with better quality and productivity
than statically typed languages.
I'm saying use low-level programming languages when you have to and
high-level programming languages when you can.
Using high-level/dynamically-typed, and low-level/statically-typed as
synonyms seems pretty wrong. There are very low-level dynamically typed
languages (Forth), and very high-level statically typed ones (Haskell).

Taylan
Panicz Maciej Godek
2014-09-06 23:46:07 UTC
Permalink
Post by Marko Rauhamaa
Post by Panicz Maciej Godek
However, I'd rather say that the lack of any type system in Guile is
an inconvinience, because static type checking allows to avoid a huge
class of software errors, and a good type system (like the one in
Haskell) actually enhances language's expressiveness.
We already have a satisfactory selection of languages with static type
annotation. The primary upside of static types is much faster code. The
downside is boilerplate and clutter that make it a huge chore to write
and maintain the code.
Taylan already wrote a few remarks on that statement. Obviously, when
you're talking about statically typed languages, you mean C, Pascal,
and its derivatives. However, you're mistaken. Haskell or ML are also
statically typed, but because of type inference, they do not introduce
any boilerplate nor clutter.

The fact that C compiler performs static type checking has nothing to
do with its performance. It's only about detecting type errors. So for
example if you have code like:

short f();

long g() {
return f();
}

the compiler will generate an error. An alternative would be to
compile according to specification and let the user worry about the
problems caused by type mismatch (and I think that this is what the
early C compilers were doing)
Post by Marko Rauhamaa
In my experience, high-level programming
languages allow you to accomplish more challenging feats with better
quality and productivity than statically typed languages.
In addition to Taylan's remark, my experience is that in large
programs it's very easy to make a type error, and it may take some
time for that bug to manifest, and because of that latency such bugs
become more confusing and harder to trace. Furthermore, having type
signatures often make complex programs easier to read.
Marko Rauhamaa
2014-09-07 00:20:52 UTC
Permalink
Post by Panicz Maciej Godek
The fact that C compiler performs static type checking has nothing to
do with its performance. It's only about detecting type errors. So for
short f();
long g() {
return f();
}
the compiler will generate an error.
(It actually doesn't. Try it.)

You are right that C compilation would simply not be possible without
static type information present. However, since Scheme can do everything
C can without static type information, the principal justification for
its existence is performance. That's why Guido van Rossum is tempted to
add optional static type annotation to Python: it would make it possible
replace Java/C# with Python.


Marko
Taylan Ulrich Bayirli/Kammer
2014-09-07 12:57:05 UTC
Permalink
[...] However, since Scheme can do everything C can without static
type information, the principal justification for its existence is
performance. [...]
I think that's a wrong way to look at it. Scheme has a type system too;
a dynamic/run-time one. The "default" situation would be to have no
type system at all, where you can add 5 to "foo" and use the result in a
floating-point operation where it counts as 1.8142093e-316. That's
confusing if you do it by accident, so we use type systems to prevent us
from it, which is the principal justification for their existence. The
choice between a static and a dynamic one is then influenced by their
performance characteristics, the time at which they can tell you your
mistake (compile time vs. run time), how readable they make the code
(some say manifest types make code clearer), etc.

Sorry for being pedantic. :)

Taylan
Marko Rauhamaa
2014-09-07 13:58:51 UTC
Permalink
[...] However, since Scheme can do everything C can without static
type information, the principal justification for its existence is
performance. [...]
I think that's a wrong way to look at it. Scheme has a type system
too; a dynamic/run-time one.
You are changing the topic a bit.
The "default" situation would be to have no type system at all, where
you can add 5 to "foo" and use the result in a floating-point
operation where it counts as 1.8142093e-316.
It's perfectly fine to avoid errors by generalizing semantics so I
wouldn't mind if you did what you propose. However, the dynamic type
system is necessary for the simple fact that you will need to define
runtime semantics.

As a less convoluted example you could take classic λ calculus or the
abstract set theory, whose entities could really be considered typeless
(no integers, no strings, no files etc).
That's confusing if you do it by accident, so we use type systems to
prevent us from it, which is the principal justification for their
existence.
No, the primary objective is not to prevent errors but to have
well-defined semantics. Scheme, Python, C or Java would function
perfectly well without any type error checking, static or dynamic. The
results could be undefined or a burning computer, that doesn't matter.
What matters is that you know what a well-defined program is supposed to
do.
The choice between a static and a dynamic one is then influenced by
their performance characteristics, the time at which they can tell you
your mistake (compile time vs. run time), how readable they make the
code (some say manifest types make code clearer), etc.
Yes, early static error checking is a happy side effect of a static type
system.

However, in my extensive practical experience, a static type system,

* makes writing a program a huge chore,

* invites lazy shortcuts and accidental mistakes,

* often makes the logic of the program very hard to follow.

Moreover, coming up with the correct type annotation can become a
frustrating metaphysical exercise. For example, C++ calls for template
acrobatics and C's const keyword is impossible to get right (and nobody
even tries anymore).

I'm seriously saying Python or Scheme programs make it much easier to
write and maintain very complex software systems than C, C++ or Java
even though they don't catch early type or call signature violations.
IOW, you sacrifice a little bit of quality and gain a lot.

The only remaining consideration is performance. There still remains an
ever dwindling niche where dynamic programming languages just aren't
performant enough.
Sorry for being pedantic. :)
No harm done.


Marko
Taylan Ulrich Bayirli/Kammer
2014-09-07 16:46:44 UTC
Permalink
Post by Marko Rauhamaa
It's perfectly fine to avoid errors by generalizing semantics so I
wouldn't mind if you did what you propose. However, the dynamic type
system is necessary for the simple fact that you will need to define
runtime semantics.
Oh, I was rather looking at things from a low-level perspective. E.g.
the "foo" string in my example was meant as a pointer, whose numeric
value plus 5 resulted in an integer whose underlying byte sequence is
then interpreted as an IEEE double.

In other words I was suggesting that "by default" there are only byte
sequences, and type systems help to work with these conveniently.
Post by Marko Rauhamaa
No, the primary objective is not to prevent errors but to have
well-defined semantics. Scheme, Python, C or Java would function
perfectly well without any type error checking, static or dynamic. The
results could be undefined or a burning computer, that doesn't matter.
What matters is that you know what a well-defined program is supposed
to do.
I think I understand your viewpoint, and it also makes sense: types
might not be essential to bit-crunching, but they are to abstract models
of computation.
Post by Marko Rauhamaa
However, in my extensive practical experience, a static type system,
[...]
Does that experience cover languages like SML, Ocaml, and Haskell? (Not
a rhetorical question, though I suspect it doesn't; at least not as much
as languages like C, C++, and Java.)

Taylan
Marko Rauhamaa
2014-09-07 19:49:43 UTC
Permalink
Post by Taylan Ulrich Bayirli/Kammer
Post by Marko Rauhamaa
However, in my extensive practical experience, a static type system,
[...]
Does that experience cover languages like SML, Ocaml, and Haskell?
I have had superficial experience with SML way back (late 80's). No idea
how the languages would work in practice. However, I don't feel I'm
missing anything in Scheme.
Post by Taylan Ulrich Bayirli/Kammer
(Not a rhetorical question, though I suspect it doesn't; at least not
as much as languages like C, C++, and Java.)
Not anywhere near as much. What do you have to say about those
languages?


Marko
Taylan Ulrich Bayirli/Kammer
2014-09-07 23:13:28 UTC
Permalink
Post by Marko Rauhamaa
Not anywhere near as much. What do you have to say about those
languages?
Just what I said previously: many users of languages with more
sophisticated static type systems like Ocaml or Haskell say they prefer
that over dynamic typing. I don't have much experience myself, though
at times I also felt positive about the ability to encode some logic in
a static type system.

Taylan

Nala Ginrut
2014-09-06 16:57:02 UTC
Permalink
Post by Panicz Maciej Godek
Post by David Thompson
http://elm-lang.org/learn/What-is-FRP.elm
Using FRP, we can model with mutable state in a pure, functional way.
OTOH, when you take a look at the example code (Mario), you can trace
the notion of objects. E.g.
mario = { x = 0, y = 0, vx = 0, vy = 0, dir = "right" }
What else is that, if not an object?
"Well, it's a structure", one could say -- because it has no methods.
However, this is just what the most rudimentary GOOPS objects are -- a
named tuple (provided that you use no virtual slots). I think that it
is a big problem of Scheme, that it does not have any noncontroversial
and commonly accepted way for creating named tuples.
Doesn't assoc-list fill the gap?
Post by Panicz Maciej Godek
Furthermore, instead of using explicit side effects, as one would
normally do, the Mario example first defines a step function, and
calls "foldp step mario input". Although I do appreciate efforts like
in "How to Design Worlds" book or "Introduction to Systematic Program
Design" course, to avoid explicit mutation (because as SICP shows, it
complicates the model of computation), I don't see so many benefits of
avoiding mutation in complex realtime systems.
I partly agree with you. For stateless makes things complex. Yes, it could
be complex.
But, no, it depends on need and scenario. The advantage of stateless is to
provide a more reliable and understandable system to users and maintainers.
If your system is constrained by hard realtime need, go ahead with
side-effect, it's cool. But most of the time, it's not the story from
reasonable users.
If you doubt stateless party exaggerated the truth, I'll recommend this
paper: <<Out of the tar pit>>
Post by Panicz Maciej Godek
Actually, when I look at the Mario example, I have a feeling that the
code would be much cleaner and easier to follow if it was written in a
more traditional imperative/callback style.
It's fine if you think imperative is still cool in FP, me too, since most
people living in non-FP land.
But if you're expecting imperative way in FP land rather than learning and
trying it in FP way, why not choose non-FP lang for it? :-)
Continue reading on narkive:
Loading...