Python syntax in Lisp and Scheme

P

Pascal Bourguignon

Tim Hochberg said:
Pascal said:
Marcin 'Qrczak' Kowalczyk said:
On Tue, 07 Oct 2003 21:59:11 +0200, Pascal Bourguignon wrote: [SNIP]
A richer alphabet is often more readable. Morse code can't be read as fast
as Latin alphabet because it uses too few different symbols. Japanese say
they won't abandon Kanji because it's more readable as soon as you know it -
you don't have to compose words from many small pieces which look alike
but each word is distinct. Of course *too* large alphabet requires long
learning and has technical difficulties, but Lisp expressions are too
little distinctive for my taste.
Well, I would say that kanji is badly designed, compared to
latin
alphabet. The voyels are composed with consones (with diacritical
marks) and consones are written following four or five groups with
additional diacritical marks to distinguish within the groups. It's
more a phonetic code than a true alphabet.
[SNIP]

Admittedly, I found the above paragraph pretty hard to parse and my
never stellar knowledge of Japanees has mostly evaporated over time,
but I'm pretty sure you are talking about Hiragana (or Katakana), not
Kanji. Japaneese has three alphabets, which they mix and match in
ordinary writing. Kanji aren't phonetic at all, they're ideograms, and
can typically be read at least two completely different ways depending
on the context, making reading Japanese extra confusing for the non
fluent.

Absolutely. My mistake, sorry. I wrote about katakana and that was
not the subject.
 
P

Pascal Bourguignon

Excerpts from real C header files:

#define FromHex(n) (((n) >= 'A') ? ((n) + 10 - 'A') : ((n) - '0'))
#define StreamFromFOURCC(fcc) ((WORD) ((FromHex(LOBYTE(LOWORD(fcc))) << 4) + (FromHex(HIBYTE(LOWORD(fcc))))))
#define ToHex(n) ((BYTE) (((n) > 9) ? ((n) - 10 + 'A') : ((n) + '0')))
#define MAKEAVICKID(tcc,stream) MAKELONG((ToHex((stream) & 0x0f) << 8) | (ToHex(((stream) & 0xf0) >> 4)),tcc)

#define va_arg(AP, TYPE) \
(AP = (__gnuc_va_list) ((char *) (AP) + __va_rounded_size (TYPE)), \
*((TYPE *) (void *) ((char *) (AP) \
- ((sizeof (TYPE) < __va_rounded_size (char) \
? sizeof (TYPE) : __va_rounded_size (TYPE))))))

#define PLAUSIBLE_BLOCK_START_P(addr, offset) \
((*((format_word *) \
(((char *) (addr)) + ((offset) - (sizeof (format_word)))))) == \
((BYTE_OFFSET_TO_OFFSET_WORD(offset))))


:)


However, taking sources written by the same programmer (me), at about
the same period, I get thrice or twice as many parenthesis in Lisp
than in C:

------- ----- ----- ------------------------------------
(-paren loc (/lin Language
------- ----- ----- ------------------------------------
160 / 627 = 0.255 COBOL sources.
6697 / 18968 = 0.353 C sources (.h + .c)
327 / 825 = 0.396 Java sources.
9071 / 18968 = 0.478 C sources, counting both ( and {
15 / 31 = 0.484 FORTRAN-IV (I've got only one toy program).
15224 / 29990 = 0.508 Modula-2 sources (* comments! *).
14754 / 13890 = 1.062 Lisp sources.
------- ----- ----- ------------------------------------

That's not much more...




for f in *.{h,c,lisp} ; do printf "%6d " $( sed -e 's/[^(]//g'<$f|tr -d '\012'|wc -c ) ; wc $f ; done|awk '{p=$1;l=$2;w=$3;c=$4;n=$5;printf "%6d / %6d = %6.3f %s\n",p,l,p/l,n;pt+=p;lt+=l;}END{printf "%6d / %6d = %6.3f %s\n",pt,lt,pt/lt,"Total";}'
 
H

Hans Nowak

Doug said:
If you truly believe what you are saying, you really should be
programming in Java. Everything is explicit, [...]

<nitpick>
Not really. 'this' is implicit, for example. In fact, Java people have been
known to criticize Python because it's 'self' construct is explicit. ^_^
</nitpick>
 
P

Pascal Bourguignon

You misunderstand me. In a python block, two expressions are
associated with each other if they are the same distance from the left
edge. This is isomorphic to having a nametag identifying the scope
of the line. Lines are associated with each other iff they have the
same nametag. Change one, and all must change.

Exactly. What was that language where you wrote tags to indicate the
indenting of data structures:

01 DREP.
02 NO-REPR PIC 9999.
02 NO-SOTR PIC 9999.
02 NOM PIC X(20).
02 PRENOM PIC X(15).
02 A PIC 9.
02 B PIC 9.
02 FILLER PIC X.
02 D PIC 9.
02 FILLER PIC X(33).
01 DVTE.
02 NO-SOTV PIC 9999.
02 NO-REPV PIC 9999.
02 MTV PIC 9(6)V99.
02 FILLER PIC X(64).

If, instead, you use balanced delimiters, then a subexpression no
longer has to encode its position within the containing expression.
[...]
 
B

Bengt Richter

Actually, I meant more lazy-like-lazy-in-Haskell. Infinite data
structures and such. "primes" being a list representing _all_ prime
numbers for instance. You can build this as soon as you have closures
but making the construction easy to use for the application programmer
might be a challenge without macros. But I don't know what
"properties" are in Python, possibly they are built for exactly that.
No, generators are closer to "exactly that" e.g., in the following function,
"yield" is the keyword that makes it into a generator. The initial call effectively
becomes a factory function call that returns an initialized generator, and calls
to the generators' .next() method resumes execution first at the beginning, running
up to the first yield, where execution is suspended and the yield value returned to
the next() method caller. A subsequent .next() call resumes execution right after the
last yield, and so on forever or until the generator exits without hitting a yield,
which terminates the sequence.
... import array
... primes = array.array('l',[2])
... yield 2
... for prime_candidate in xrange(3,prime_range_top,2):
... for p in primes:
... if prime_candidate%p==0: break
... else:
... primes.append(prime_candidate)
... yield prime_candidate
... 2

Properties allow you to create a class whose instances have a property attribute that
is accessed just like an ordinary attribute, but may invoke arbitrary functions to get
and/or set the apparent state. E.g.,
... def __init__(self, k, ptop):
... self.k = k
... self.pnext = lazyprimes(ptop).next
... p = property(lambda self: self.pnext())
...
Here k is an ordinary instance attribute and p looks like another in the syntax of the
access in an expression, but it is hooked into a lazy prime generator:
>>> foo.k 123
>>> foo.p 2
>>> foo.p, foo.p (3, 5)
>>> foo.k 123
>>> [(foo.k, foo.p, c) for c in 'abcd'] [(123, 7, 'a'), (123, 11, 'b'), (123, 13, 'c'), (123, 17, 'd')]
>>> foo.pnext() 19
>>> foo.pnext() 23
>>> foo.p 29
>>> foo.p 31
>>> foo.p
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 5, in <lambda>
StopIteration

Well, I should have provided for dealing with the end-of-sequence exception, most likely,
unless reaching it was an error. Not hard.

Getting back to primegen, which last yielded the first prime 2 above,
I'll bind a shorter name to the next method (bound to the particular generator),
for easier typing ;-)
3
Here we'll enumerate ...
0 5
1 7
2 11
3 13
4 17
5 19
6 23
7 29 ...
41 43 47 53 59 61 67 71 73 79 ...
97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 2
27 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 3
67 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 5
09 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 6
61 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 8
29 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997
(In opportune line wrap on my console screen, but I assume it's clear enough).
No. I was thinking about Prolog and such. Or nondeterministic
programming. Or multimethod dispatch.
For the latter in Python, see
http://www-106.ibm.com/developerworks/linux/library/l-pydisp.html
I was not talking about generating code /in/ Python but generating
code /for/ Python /within/ it. For the Design Pattern use case take a
look at http://norvig.com/design-patterns/
I was also talking about "generating codef /for/ Python /within/ it" -- even in
a recrusive routine, see answer to 4. below ;-)
You can change Python's syntax? Easily?
No, I didn't mean that literally, but just as you can create source dynamically
and compile it for subsequent execution (as with the trivial text source template below),
you could define an app-specific mini language and translate and compile it for use later
in the same program run. If you create a class and populate class variables from values
in a config file, are you doing this? I think so, at a trivial level. If you wrote an
import function that could import a subset of scheme and have it dynamically translated
to something that looks like a python module to the python user, have you changed python's
syntax? No. Have you made other syntax available to the python programmer? Yes.

I had an infatuation with scheme, some years back now. I still think she was sweet ;-)
I'm not sure I understand.
... fooname = 'foo_level_%s'%level
... source = """
... def %s(): print 'I was compiled at level %s on %s.'
... """% (fooname, level, time.ctime())
... d={}
... exec source in d
... time.sleep(2) # to guarantee time stamp change
... if level<maxrec: return (d[fooname],)+ multicomp(maxrec,level+1)
... return (d[fooname],)
... ...
My name is 'foo_level_0' and I was compiled at level 0 on Tue Oct 07 21:19:18 2003.
My name is 'foo_level_1' and I was compiled at level 1 on Tue Oct 07 21:19:20 2003.
My name is 'foo_level_2' and I was compiled at level 2 on Tue Oct 07 21:19:22 2003.
My name is 'foo_level_3' and I was compiled at level 3 on Tue Oct 07 21:19:24 2003.
My name is 'foo_level_4' and I was compiled at level 4 on Tue Oct 07 21:19:26 2003.
My name is 'foo_level_5' and I was compiled at level 5 on Tue Oct 07 21:19:28 2003.
Ok, the recursion was gratuitous, except that it shows compilation happening dynamically,
and you can easily see you could leave such routines compiled at the outer level for
execution any time you wanted, and thus get "many compile times" ;-)
Python is pretty sharp ;-)
I think we need some realistic use cases for your "specific" [categories of]
examples in order to compare how problems would be approached.

Well, if you don't want to learn about syntactic abstraction you'll
probably never miss it during your programming. Just keep in mind
that before oo-abstraction became fashionable people didn't miss OOP
either.

Actually, I would like to learn more about it. I am fascinated by the
interplay between the worlds of concrete representations and abstract entities,
and their interrelated transformations. ISTM macros definitely have a place in the pantheon.
I have yet to grok scheme's hygienic macro stuff, though ;-) One of these days...

Regards,
Bengt Richter
 
G

Greg Ewing (using news.cis.dfn.de)

Daniel said:
Haven't some people implemented an entire class system as one huge macro?

YES! Been there, done that -- about 3 or 4 times, actually.
I went through a bit of a phase where writing OO implementations
for Scheme was one of my principal hobbies. :)

By the way, Scheme was my Favourite Cool Language for quite
a while. Then I discovered Python, and while I still appreciate
all the things about Scheme that I appreciated then, I wouldn't
want to go back to using it on a regular basis now. So it's not
a given that any person who likes Scheme must inevitably dislike
Python!

I do "get" macros, and I appreciate having them available in
languages like Scheme, where they seem to fit naturally. But
I can't say I've missed them in Python, probably because Python
provides enough facilities of its own for constructing kinds of
mini-languages (keyword arguments, operator overloading,
iterators, etc.) to satisfy my needs without having to resort
to macros.

And I do regard macros as something that one "resorts" to, for
all the reasons discussed here, plus another fairly major one
that nobody has mentioned: Unless both the macro system and
the macros written in it are *extremely* well designed, error
reporting in the presence of macros of any complexity tends to
be abysmal, because errors get reported in terms of the expansion
of the macro rather than what the programmer originally wrote.

ALL macro systems of any kind that I have ever used have suffered
from this - cpp, C++ templates, Lisp/Scheme macros, TeX,
you name it. I'd hate to see Python grow the same problems.
 
G

Greg Ewing (using news.cis.dfn.de)

Hans said:
Hmm, if I recall correctly, in Latin the plural of 'virus' is 'virus'.

Actually, the last discussion of this that I saw (can't remember where)
came to the conclusion that the word 'virus' didn't *have* a plural
in Latin at all, because its original meaning didn't refer to something
countable.
 
N

Ng Pheng Siong

According to Pascal Bourguignon said:
Well, I would say that kanji is badly designed, compared to latin
alphabet. The voyels are composed with consones (with diacritical
marks) and consones are written following four or five groups with
additional diacritical marks to distinguish within the groups. It's
more a phonetic code than a true alphabet.

Kanji are ideograms borrowed from Chinese. Kanji literally means "Han
character".

I think the diacritical marks you mention are pronunciation guides, much
like Hanyu Pinyin is a Mandarin pronunciation guide for Chinese.

In Hanyu Pinyin, Kanji (read as a Chinese word phrase) is rendered "han4
zi4".

In Korean, Kanji is pronounced Hanja.

Same two-character word phrase, different pronunciations.
 
T

Thomas F. Burdick

well, there are a few occasions where symbols are preferrable. just
imagine mathematics with words only

Oh, certainly. Unlike most languages, Lisp lets you use symbols for
your own names (which is easily abused, but not very often). A bad
example:

;; Lets you swear in your source code, cartoonishly
(define-symbol-macro $%^&!
(error "Aw, $%^&! Something went wrong..."))

;; An example use
(defun foo (...)
(cond
...
(t $%^&!)))

And, although you generally use symbols from the KEYWORD package for
keyword arguments, you don't have to, and they don't have to be words:

(defgeneric convert-object (object new-type)
:)documentation "Like an extensible COERCE."))

(defun convert (object &key ((-> to)))
"Sugary"
(convert-object object to))

(defconstant -> '-> "More sugar")

;; Example usage
(convert *thing* -> (class-of *other-thing*))

Of course, these are lame examples, but they show that Lisp *can*
incorporate little ascii-picture-symbols. Good examples would
necessarily be very domain-dependant.

--
/|_ .-----------------------.
,' .\ / | No to Imperialist war |
,--' _,' | Wage class war! |
/ / `-----------------------'
( -. |
| ) |
(`-. '--.)
`. )----'
 
D

Daniel P. M. Silva

Greg said:
YES! Been there, done that -- about 3 or 4 times, actually.
I went through a bit of a phase where writing OO implementations
for Scheme was one of my principal hobbies. :)

Nice! I was alluding to MzScheme's class.ss but I guess that's a fun hobby
to have. :) Do you have your class systems available anywhere to download?
I would be especially interested in them if they allow multiple
inheritance, run-time pillaging of class contracts, and explicit "this"
arguments to methods...

By the way, Scheme was my Favourite Cool Language for quite
a while. Then I discovered Python, and while I still appreciate
all the things about Scheme that I appreciated then, I wouldn't
want to go back to using it on a regular basis now. So it's not
a given that any person who likes Scheme must inevitably dislike
Python!

I do "get" macros, and I appreciate having them available in
languages like Scheme, where they seem to fit naturally. But
I can't say I've missed them in Python, probably because Python
provides enough facilities of its own for constructing kinds of
mini-languages (keyword arguments, operator overloading,
iterators, etc.) to satisfy my needs without having to resort
to macros.

You still can't add new binding constructs or safe parameterizations like a
with_directory form:

with_directory("/tmp", do_something())

Where do_something() would be evaluated with the current directory set to "
tmp" and the old pwd would be restored afterward (even in the event of an
exception).

Last year -- I think at LL2 -- someone showed how they added some sort of
'using "filename":' form to Python... by hacking the interpreter.
And I do regard macros as something that one "resorts" to, for
all the reasons discussed here, plus another fairly major one
that nobody has mentioned: Unless both the macro system and
the macros written in it are *extremely* well designed, error
reporting in the presence of macros of any complexity tends to
be abysmal, because errors get reported in terms of the expansion
of the macro rather than what the programmer originally wrote.

I've yet to encounter this problem in the standard library included with my
Scheme implementation of choice, but ok.
ALL macro systems of any kind that I have ever used have suffered
from this - cpp, C++ templates, Lisp/Scheme macros, TeX,
you name it. I'd hate to see Python grow the same problems.

Some people use Python's hooks to create little languages inside Python (eg.
to change the meaning of instantiation), which are not free of problems:

class Object(object):
def __init__(this, *args, **kwargs):
this.rest = args
this.keys = kwargs

def new_obj_id(count=[0]):
count[0] = count[0] + 1
return count[0]

def tag_obj(obj, id):
obj.object_id = id
return obj

def obj_id(obj): return obj.object_id

type.__setattr__(Object, "__new__", staticmethod(lambda type, *args:
tag_obj(object.__new__(type), new_obj_id())))


Great, now all object instantiations (of our own Object class) will also tag
new objects with an ID:

obj = Object()
print "id: ", obj_id(obj)
print "another id: ", obj_id(Object())

Which gives you 1 and then 2. Hurrah.
Have you caught the bug yet?

# forgot to check for this case...
print Object(foo="bar")

This is of course illegal and I get the following error message:

Traceback (most recent call last):
File "n.py", line 27, in ?
print Object(foo="bar").rest
TypeError: <lambda>() got an unexpected keyword argument 'foo'

Hmm...
 
C

Carlo v. Dango

I'd humbly suggest that if you can't see *any* reason why someone
would prefer Lisp's syntax, then you're not missing some fact about
the syntax itself but about how other language features are supported
by the syntax.

sure, but it seems like noone was able to let CLOS have
(virtual) inner classes,
methods inside methods,
virtual methods (yeah I know about those stupid generic functions :),
method overloading,
A decent API (I tried playing with it.. it doesn't even have a freaking
date library as standard ;-p

Yes I agree with the compile time macro expansion is a nice thing.
However, if I want to do some serious changes to the structure of objects
and classes (i.e. create a new kind of objects) then I have to spend a
long time finding out how the CLOS people hacked together their
representation of classes, methods, method call etc... it has been far
easier for me to just do some small changes using __getattribute__ and
metaclasses in python. So in that respect Im not really sure the macro
idea is advantageous for other than 'straight away' macros...

yes this mail is provocative.. please count slowly to 10 before replying
if you disagree with my point of view (and I know Pascal will disagree ;-)
.... not that I ever seen him angry ;-)


Carlo van Dango
 
D

David Rush

Rather like Lispers ;)

Very -ish. Kanji is pictographic and the Japanese borrowed their usage
from China several times over the course of a thousand years so from
what a westerner might call reading POV, it's a mess. Having learned
to read Kanji; however, I have to say that the leverage you get from
the pictograms is amazing. I found myself quite able to navigate myself
to (and through) government offices whose name I didn't even begin to
know,but whose function was clear from the kanji on the door.

Indeed Japanese children spend most of gradeschool learning the first 2000
or so Kanji. By the time you finish university it can be necessary to know
up to 10,000 (or so my wife tells me).

I'll grant that Lisp is rough in vanilla VI, but who uses that anymore?
Syntax coloring and auto-indenting make it virtually identical to Python.
I would go so far as to say that I *read* lisp via indentation.
Well, I would say that kanji is badly designed, compared to latin
alphabet. The voyels are composed with consones (with diacritical
marks) and consones are written following four or five groups with
additional diacritical marks to distinguish within the groups. It's
more a phonetic code than a true alphabet.

Sorry, but that's all any alphabet is. The Kana are particularly aptly
suited to the Japanese language which is phonetically *very* simple. The
Kana encode the basic syllables - *all* of them. English/Latin is a
disaster by comparison.

All of which goes to show something like: languages make sense in the
context
where they are used - otherwise they wouldn't be used...

david rush
 
J

james anderson

Carlo v. Dango said:
sure, but it seems like noone was able to let CLOS have
(virtual) inner classes,
methods inside methods,
virtual methods (yeah I know about those stupid generic functions :),
method overloading,
A decent API (I tried playing with it.. it doesn't even have a freaking
date library as standard ;-p

Yes I agree with the compile time macro expansion is a nice thing.
However, if I want to do some serious changes to the structure of objects
and classes (i.e. create a new kind of objects) then I have to spend a
long time finding out how the CLOS people hacked together their
representation of classes, methods, method call etc... it has been far
easier for me to just do some small changes using __getattribute__ and
metaclasses in python. So in that respect Im not really sure the macro
idea is advantageous for other than 'straight away' macros...

yes this mail is provocative.. please count slowly to 10 before replying
if you disagree with my point of view (and I know Pascal will disagree ;-)
... not that I ever seen him angry ;-)

one might benefit more from reasoned examples, comparisons, and questions than
from vacuous vitriol.

....
 
M

Mario S. Mommer

Intrestingly enough, I think this is a question of getting used to
it. The notation is so relentlessly regular that once you got it,
there are no more syntactical ambiguities. None. Ever.

It is the difference (for me) between reading roman numerals (that
would be the baroque-ish syntax of other languages, full of
irregularities, special cases, and interference patterns), and arabic
numerals (that would be lisp). I never have a doubt about what the
S-expr. representation encodes.
 
D

David Rush

But Lisp's syntax is not the way it is to make the compiler writer's
job easier. <macros> *That's* why we don't mind, and, in
fact, actively like, Lisp's syntax.

In fact, I have also noticed that programming in nearly all other languages
(Smalltalk, APL, and FORTH are the exceptions) tends to degenerate towards
a fully-parenthesized prefix notation in direct proportion to the size of
the code. This fully-parenthesized prefix notation is just everyday
function calls, BTW. Method invocations aren't really any different.

So if you're going to write in parenthesized prefix notation *anyway*, you
might as well get some benefit out of it -> s-expressions and macros

david rush
 
M

Matthias

Marcin 'Qrczak' Kowalczyk said:
All Lisp code I've read uses lots of parentheses
and they pile up at the end of each large subexpression so it's hard to
match them (an editor is not enough, it won't follow my eyes and won't
work with printed code).

The paranthesis argument. Are there really no new things under the sun? ;-)

Well, in Lisp as in most other languages (esp. Python) you read source
code by indentation, not by matching parentheses. That is why some
Lispers are a bit intolerant against non-standard indentation. They
use it (mentally) to parse the language. The parenthesis really are a
personal-taste-only issue.
 
D

Dirk Thierbach

james anderson said:
is the[re] no advantage to being able to do either - or both - as the
occasion dictates?

Of course it's good to be able to do either. And Lisp macros are a
powerful tool, and it's good to have it when you need it. But it's
somewhat annoying to frequently read grossly exaggerated claims like
"lots of things you can do with Lisp macros are impossible to do in
any other language" when you can do a good part of these things even in
Lisp without macros.
while the first assertion might well be born out by a statistical
analysis of, for example, open-source code, i'm curious how one
reaches the second conclusion.

I don't have statistical evidence, this is just my personal
impression. On the one hand, there are the above claims, on the other
hand, I see code that would (IMHO) much more simple and elegant with
HOFs, but instead imperative assignment is used, or macros. So I then
I have the impression that the programmer doesn't really know how to
use HOFs, otherwise he would have used them. Maybe that's wrong and
they all do that on purpose, or because they don't like HOFs (like you
seem to do), but somehow this is difficult to imagine.
when did common-lisp macros become an "additional language construct"?

It's "additional" in the sense that you can write programs without it,
and that different Lisp dialects use a different syntax and semantics
for macros. HOFs on the other hand are pure lambda calculus, and every
functional language has them.
doesn't that last phrase contradict the previous one?

I don't see any contradiction; maybe you can be more explicit?
i do admit to infrequent direct use of higher-order functions. one
reason is that there is little advantage to articulating the
creation of functions which have dynamic extent only,

Why? The whole point (or one of them) of functional languages is that
functions are first class, and you can easily use them (and you
frequently do) in 'map', 'fold', or more complicated HOFs. It's simple,
elegant, reusable, type-safe, and avoids unecessary state. A definition
like

sum = foldr (+) 0

in Haskell is a lot better than doing an explicit loop. If you don't
use HOFs at all, then IMHO you're not doing proper functional
programming.
so in my use, most hof's are manifest through a macro
interface. it's the same distaste i have about inner and anonymous
java classes.

I don't like inner and anonymous classes either: They're just a clumsy
substitute for anonymous functions, and they have too much syntactic
garbage associated with them.
the other reason is that when i moved from scheme to lisp, in the
process of porting the code which i carried over, it occurred to me
that much of what i was using higher-order functions for could be
expressed more clearly with abstract classes and appropriately
defined generic function method combinations.

Sometimes it is more convenient to use other forms of parametrization
(like classes). Sometimes HOFs are more natural. It really depends on
the concrete example. And of course 'classes' or similar language
features are independent from macros, and many languages provide them,
even if they don't have macros.

- Dirk
 
M

Marcin 'Qrczak' Kowalczyk

Python removes this significant problem, at as far as I'm aware no real cost
and plenty of additional gain (less visual clutter, no waste of delimiter
characters ('{','}') or introduction of keywords that will be sorely missed as
user-definable names ('begin', 'end')).

There are three choices for a lanuage syntax:
1. Line breaks and indents are significant (Haskell, Python).
2. Line breaks only are significant (Ruby, Unix shell, Visual Basic).
3. Neither is significant (most languages).

I found the syntax of Haskell and Python aesthetic, and tried to introduce
significant whitespace into my own little language. It was surprisingly hard.

The first attempt used a quite minimalistic syntax and had significant
indents. In effect indentation errors usually went undetected and the
program suddently had a different meaning. Since you wouldn't want to
consistently use indentation for all local functions, they used either
braces or indentation - but not both! so it looked very differently
depending on whether you wanted to make use of significant indents or not.
And since it was a functional language, it used quite a lot of nesting.
I quickly abandoned this version.

Although misindenting Haskell code can produce a valid parse, the error
is usually caught either by scoping rules or by the typechecker; my
language was dynamically typed. Haskell doesn't have the "inconsistency"
problem because when you omit a line break which would introduce or close
indentation, you usually don't have to insert braces - syntax rules say
that virtual closing braces are inserted when not inserting it would cause
a parse error. Unfortunately this rule is almost impossible to implement
correctly (current compilers fail to use it correctly in some subtle cases).
There are cases when the language requires a different indentation than
I would like to use (mostly 'if-then-else' and 'let' inside 'do').

Python has a simpler syntax, where indentation is used on the level of
statements as the only delimiting mechanism, and not on the level of
expressions - which can't contain statements. It doesn't allow to replace
indentation with explicit delimiters. Since most expressions have pending
open parens or brackets when cut in the middle (because of mandatory
parens around function arguments), most line breaks inside expressions are
identifiable as insignificant without explicit marking. So it's easy to
design rules which use indentation, at the cost of the inability to
express various things as expressions. It's an imperative language and
such syntax won't fit a functional language where you would want to have
a deeper nesting, and where almost everything can be used inside an
expression.

Moral: Haskell and Python happen to succeed with significant indents
but their rules are hard to adapt to other languages. Significant
indentation constrains the syntax - if you like these constraints, fine,
but it would hurt if a language were incompatible with these constraints.

Having failed with significant indents, I tried to use significant line
breaks in next incarnations of my language, which looked like a good
compromise. The language had a richer syntax this time and it worked
quite well, except that one too often wanted to break a line in a place
which had to be explicitly marked as an insignificant break. I had
troubles with designing a good syntax for some constructs, mainly
if-then-else, being constrained to syntaxes which can be nicely split
into lines.

After experimenting with various syntaxes which used significant line
breaks to separate declarations and statements (delimiting was first done
by an opening word and 'end', later by braces), I tried how it would look
like with explicit semicolons. Surprisingly this opened new ways to build
some syntactic constructs. I finally got an 'if' which I was happy with,
I was no longer forced to choose to either not break a particular long
line or to mark the line break as insignificant, and I could abandon
designing a built-in syntax for catching exceptions because using a
suitable function no longer interfered with line breaking.

Moral is the same. Although designing and implementing a syntax with
significant line breaks and insignificant indentation is much easier than
with significant indentation, it still takes away some freedom of syntax
design which might be noticeable. Perhaps there are subtle ways to apply
significant line breaks to various languages, which you might find with
some luck or experience... I've given up, designing a syntax with
insignificant whitespace is much safer.
 
A

Alex Martelli

Daniel P. M. Silva wrote:
...
You still can't add new binding constructs or safe parameterizations like
a with_directory form:

with_directory("/tmp", do_something())

Where do_something() would be evaluated with the current directory set to
" tmp" and the old pwd would be restored afterward (even in the event of
an exception).

Right: you need to code this very differently, namely:
with_directory("/tmp", do_something)
*deferring* the call to do_something to within the with_directory
function. Python uses strict evaluation order, so if and when you
choose to explicitly CALL do_something() it gets called,

So, I would code:

def with_directory(thedir, thefunc, *args, **kwds):
pwd = os.getcwd()
try: return thefunc(*args, **kwds)
finally: os.chdir(pwd)

this is of course a widespread idiom in Python, e.g. see
unittest.TestCase.assertRaises for example.

The only annoyance here is that there is no good 'literal' form for
a code block (Python's lambda is too puny to count as such), so you
do have to *name* the 'thefunc' argument (with a 'def' statement --
Python firmly separates statements from expressions).
Last year -- I think at LL2 -- someone showed how they added some sort of
'using "filename":' form to Python... by hacking the interpreter.

A "using" statement (which would take a specialized object, surely not
a string, and call the object's entry/normal-exit/abnormal-exit methods)
might often be a good alternative to try/finally (which makes no provision
for 'entry', i.e. setting up, and draws no distinction between normal
and abnormal 'exits' -- often one doesn't care, but sometimes yes). On
this, I've seen some consensus on python-dev; but not (yet?) enough on
the details. Consensus is culturally important, even though in the end
Guido decides: we are keen to ensure we all keep using the same language,
rather than ever fragmenting it into incompatible dialects.

Some people use Python's hooks to create little languages inside Python
(eg. to change the meaning of instantiation), which are not free of
problems:

class Object(object):
def __init__(this, *args, **kwargs):

[invariably spelt as 'self', not 'this', but that's another issue]
this.rest = args
this.keys = kwargs

def new_obj_id(count=[0]):
count[0] = count[0] + 1
return count[0]

def tag_obj(obj, id):
obj.object_id = id
return obj

def obj_id(obj): return obj.object_id

type.__setattr__(Object, "__new__", staticmethod(lambda type, *args:
tag_obj(object.__new__(type), new_obj_id()))) ...
# forgot to check for this case...
print Object(foo="bar")

It's not an issue of "checking": you have written (in very obscure
and unreadable fashion) a callable which you want to accept (and
ignore) keyword arguments, but have coded it in such a way that it
in fact refuses keyword arguments. Just add the **kwds after the
*args. This bug is not really related to "little languages" at all:
you might forget to specify arguments which you do want your callable
to accept and ignore in a wide variety of other contexts, too.

A small but important help to avoid such mistakes is to express
your intentions more readably. Let's suppose the 'Object' class is
externally defined (so we don't want change its metaclass, which would
be a more usual approach in Python), that we know it does not
implement a significant __new__ nor __slots__, and that we want to add
to it the "tag all objects on creation" feechur, which must use/support
the also-existing and specified functions new_obj_id (for generating
new ids), tag_obj (to set them) and obj_id (to access them) -- each
of these specs is significant (we'd probably code differently if any
or all of them were changed).

Given all this, the normal way to code this functionality in Python
would be something like:

def tagging_new(cls, *args, **kwds):
new_object = object.__new__(cls)
new_id = new_obj_id()
return tag_obj(new_object, new_id)
Object.__new__ = staticmethod(tagging_new)

The use of "type.__setattr__(Object, "__new__", staticmethod(lambda ..."
in lieu of the elementarily simple "Object.__new__ = staticmethod(..."
would be quite peculiar. The use of a complicated lambda instead of
a simple def also decreases readability (and thus makes mistakes
such as forgetting that the new __new__ must also accept and ignore
**kwds more likely). Another helpful idiom is to name the first arg
of staticmethod's as 'cls', NOT 'type' (which would cause confusion
with the builtin 'type', often needed in similar contexts).

Moreover, it is possible that the auxiliary functions new_obj_id and
tag_obj were not externally specified, but, rather, coded ad hoc just
to enable the new __new__ to be written as a lambda (i.e., within the
strictures of Python's lambda -- just one expression, no statements).

If that is the case, then they might also be easily refactored out, e.g.:

class Tagger(object):
def __init__(self):
self.next_id = 0
def tag_new(self, cls, *args, **kwds):
new_object = object.__new__(cls)
self.next_id += 1
new_object.object_id = self.next_id
return new_object
tagging_new = Tagger().tag_new
Object.__new__ = staticmethod(tagging_new)

How best to subdivide the task "tag a new object" is debatable (since
a new tag should never be generated except during tagging, I do prefer
to have the generation of the tag and the affixion of the tag to an
object as inseparable -- but in some cases one might surely prefer to
have them separate from the __new__ so as to be able to tag already
existing objects -- that would, of course, be easy to achieve). But
the point is that the preferred way in Python to package up some state
(particularly mutable state) and some behavior is within a class; the
use of a mutable default argument in new_obj_id is non-idiomatic -- it
seems better to make a class for the purpose. This has the advantage
of grouping related state and behavior in ways that any Pythonista
will immediately recognize (the general advantage of using any given
language's idiomatic approach: by doing what readers expect you to do,
you make your code more readable than by being original and creative).

I have extracted the tag_new method of an instance of Tagger and
named it tagging_new in the assumption that we may want to then use
the SAME tagsequence for other classes as well (and may prefer to
avoid doing so by e.g. "OtherClass.__new__ = Object.__new__"). If
that is not the case, then merging the last two lines into

Object.__new__ = staticmethod(Tagger().tag_new)

would probably be preferable.


Alex
 
P

Pascal Costanza

Terry said:
What about dealing with an arbitrary number of filters?


[macro snipped]

What about it? Using macros for somewhat simple functions stikes me
as overkill.

You're right. The use of with-collectors makes it more appropriate to
express it as a macro, but of course, one can use a simple function when
you don't stick to with-collectors.
An example:

(function evenp)
(lambda (n) (< n 0))
(lambda (n) (> n 3)))
(-4 -2 0 2 4)
(-5 -3 -1)
(5)
(1 3)


In Python:

def multisplit(seq, *preds):
predn = len(preds)
bins = [[] for i in range(predn+1)]
predpends = [(p,b.append) for (p,b) in zip(preds,bins)]
rpend = bins[predn].append
for item in seq:
for pred,pend in predpends:
if pred(item):
pend(item)
break
else: rpend(item)
return bins

multisplit(range(-5,6), lambda i: not i%2, lambda i: i<0, lambda i:
i>3)

[[-4, -2, 0, 2, 4], [-5, -3, -1], [5], [1, 3]]

For the sake of completeness, here is the Lisp version:

(defun predicate-collect (list &rest predicates)
(let ((table (make-hash-table))
(preds (append predicates
(list (constantly t)))))
(dolist (elem list)
(loop for pred in preds
until (funcall pred elem)
finally (push elem (gethash pred table))))
(mapcar (lambda (pred)
(nreverse (gethash pred table)))
preds)))


? (predicate-collect
'(-5 -4 -3 -2 -1 0 1 2 3 4 5)
(function evenp)
(lambda (n) (< n 0))
(lambda (n) (> n 3)))

((-4 -2 0 2 4) (-5 -3 -1) (5) (1 3))


Pascal
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,172
Messages
2,570,934
Members
47,479
Latest member
JaysonK723

Latest Threads

Top