Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It seems like MDL uses both greater/less than and parens. Is this analogous to Scheme using both brackets and parens? That is, just for readability?


Angle brackets and parentheses are just two of the many flavors of brackets you'll find in MDL. First, you got your regular, "boring" brackets:

  <SETG X 0>           ;FORM
  (NORD BERT)          ;LIST
  [1 MONEY 2 SHOW]     ;VECTOR
  "Bracket-ish"        ;STRING
  { what goes here? }  ;TEMPLATE
Then you got your exciting brackets, where the first ! is mandatory but the second is optional:

  !<REST .FOO!>        ;SEGMENT
  ![2 4 8 16 32 64!]   ;UVECTOR
Then you got your hashtag brackets, which pop up in the corner of the TV screen reminding you that there's another way to CHTYPE:

  #FORM (+ 1 2)        ;"same as <+ 1 2>"
And your hashtag voucher brackets, which are really just defective angle brackets you can exchange for a free hashtag:

  <>                   ;"evaluates to #FALSE ()"
Finally, you got your bogus brackets, things that look like they could be a new kind of exciting brackets, but when you look closely they're just prefix operators that tend to appear before brackets:

  '(OSCAR WILDE)       ;"same as <QUOTE (OSCAR WILDE)>"
  %<+ 1 2>             ;"evaluate immediately"
  %%<CRLF>             ;"evaluate and discard result"
See also: https://github.com/taradinoc/mdl-docs/blob/master/docs/07-st...


<> is a FORM, which is evaluated. () is a LIST, which is not.

    <+ 1 2>          ↦  3
    '<+ 1 2>         ↦  <+ 1 2> 
    (+ 1 2)          ↦  (+ 1 2)

    <TYPE <+ 1 2>>   ↦  FIX
    <TYPE '<+ 1 2>>  ↦  FORM
    <TYPE (+ 1 2)>   ↦  LIST


So parens are just a quoted list? Seems a little unnecessary but I wasn’t alive so I guess I don’t know.


> So parens are just a quoted list? Seems a little unnecessary

It's a very useful distinction. In regular Lisp, lists are the only data type that is not self-evaluating. This leads to confusion because the serialization of a list can mean different things depending on context. Consider:

    (eval (add 1 2))
    (eval '(add 1 2))
Those forms both produce the same result when evaluated. It happens by two very different execution paths, but this is not immediately apparent when you run this code. So a beginner may well be surprised to learn that the following:

    (eval x)
    (eval 'x)
may or may not produce the same result. So it's genuinely helpful to have two types of lists, one of which is self-evaluating, and the other which isn't, and to have syntax that distinguishes the two.

Another example: all of these produce the same result:

    (eval (add 1 2))
    (eval (eval (add 1 2)))
    (eval (eval (eval (add 1 2))))
and so on. But this is true only because (+ 1 2) evaluates to 3 which is self-evaluating. Contrast that with, say:

    (eval (cons 1 2))
which immediately produces an error. Trying to figure out why "add" "works" and cons doesn't and how to "fix" this can be very frustrating for a beginner.


In MDL,

   (1 <+ 2 3> 4)   ->   (1 5 4)
   '(1 <+ 2 3> 4)  ->   (1 <+ 2 3> 4)
   '<1 <+ 2 3> 4)  ->   <1 <+ 2 3> 4>
   <1 <+ 2 3> 4>   ->   an error (probably that 5 is not structured)
Parens give list literals like in most modern languages.

An interesting thing about MDL is that both FORM and LIST have the same "PRIMTYPE" so all the usual lisp-like list manipulations apply to both. A strange thing is the way these are implemented: pretty much every object (with fixed-size allocation) has a NEXT pointer, and a FORM or a LIST is an object that has a pointer (possibly null) to the first object. Taking the tail of a list entails allocating a new LIST object that points to the second element.

You may wonder: how can an element be part of two lists at the same time? Well, you copy the object since it's in a 36-bit word. (Objects that do not fit in such a word are put in lists using a special DEFER object that points to it.)


I don't agree with your assessment; parens are not giving a list literal since (1 <+ 2 3> 4) has been reduced to (1 5 4). Calculation cannot take place in a literal, by definition.

What this is doing is binding the parentheses to a list constructor. I.e. (1 2 3) is really something like <list 1 2 3>. It's more like a quasiliteral (like Lisp's backquote).

Unfortunately, the fact that the printed representation of a list is the same as the code which calculates it makes things somewhat "muddled", pun intended.

What if we have this: '(1 2 3 (4 5 6)). What is (4 5 6) here? It can't be the list (4 5 6), because (4 5 6) is code that constructs that list. Using the same printed rep for both is a very bad idea.


I meant list literal in the Python-like sense, so apologies for the confusion there.

In MDL, parens and angle brackets denote LISTs and FORMs, respectively. These are both objects with the same PRIMTYPE (a linked list) but are tagged differently. The evaluator evaluates these objects differently from each other: a LIST has each of its elements evaluated, with the results put in a new LIST. It truly is distinct from a FORM that has the same effect.

Distinctions like this were designed to make the corresponding parts of Lisp (like what you mentioned) less confusing.

Indeed, in your quote example, that (4 5 6) is a LIST, not a FORM, avoiding the very bad idea. Quasiquotation pretty much doesn't need to exist because of the ! sequence "operator" for splicing lists into lists and forms.


>Quasiquotation pretty much doesn't need to exist because of the ! sequence "operator" for splicing lists into lists and forms.

Hmm... I guess I don't know everything quasiquoting is used for in Lisp, but in MDL and ZIL, segments don't really solve the problem that quasiquoting does when it comes to writing macros.

That is, I usually want to use macros to generate code according to a template:

  <PRSI? ,FOO>
should become

  <==? ,PRSI ,FOO>
The way to write that macro is to call FORM to build the form:

  <DEFMAC PRSI? (X)
    <FORM ==? ',PRSI .X>>
But if it's a complex block of code, and the blanks I want to fill in are deeply nested, the template quickly becomes unreadable, because every structure turns into a form, and every form turns into a call to FORM.


Yeah, it does not completely replace quasiquotation, but in my experience the main things quasiquotation solves are (1) not having to write quote marks in front of every symbol and (2) not having to append lists yourself. MDL handles the first by making atoms self-evaluating (though the flip side is that you have to use , and . everywhere), and the second is handled by sequences. I agree that something like quasiquotation would make FORM construction in macros nicer to look at, though it is pretty nice that sequences work everywhere and not just inside quasiquotations.

As an example, consider the following possible implementation of PROG1 in terms of LET's implicit PROGN. They both evaluate a sequence of forms in order, but the first returns the value of the first form, and PROGN the last:

   (DEFMACRO PROG1 (form1 &REST forms)
      (LET ((temp (GENSYM)))
         `(LET ((,temp ,form1))
             ,@forms
             ,temp)))
A MDL equivalent would be something like (though I admit I'm entirely going off documentation!):

   <DEFMAC PROG1 (form1 "ARGS" forms "AUX" (temp (ATOM "temp")))
     <FORM PROG ((.temp .form1))
        !.forms
        .temp>>
Without quasiquotation, the Lisp example would be (without capitalization this time---in Lisp symbols are auto-capitalized):

   (defmacro prog1 (form1 &rest forms)
      (let ((temp (gensym)))
         (list* 'let (list (list temp form1))
            (append forms (list temp)))))
I chose this example to demonstrate how sequences alleviate the pain of splicing things into the middle of things.

Backtick is not used in MDL, right? And you're somewhat free to extend the MDL used in Zilf, right? You could get the best of both worlds by adding either a PREFORM type or an REBUILD form. For example:

   `< ... >  or   `< ... `>    ->   #PREFORM  ( ... )
or

   `struct   ->   <REBUILD struct>
The semantics would be that a PREFORM would be evaluated like a LIST, but then it would CHTYPE to FORM. Or, a REBUILD is an FSUBR that expects a structure, and it maps EVAL over the elements of that structure.

The macro would look like

   <DEFMAC PROG1 (form1 "ARGS" forms "AUX" (temp (ATOM "temp")))
     `<PROG ((.temp .form1))
        !.forms
        .temp>>
I don't know all the consequences of this change to MDL, though.


Interesting. I did find an implementation of backquoting in MDL, kind of:

https://github.com/PDP-10/muddle/blob/master/mim/development...

It doesn't run on any available MDL implementation, because it's actually for MIM, an even more obscure and undocumented extension of MDL. ZILF implements some parts of MIM, but doesn't implement READ-TABLEs at all, and the format of the table implied by that code is different from what's documented for MDL.


This is described in glorious high-definition ASCII art in Appendix 1 of The MDL Programming Language:

https://github.com/taradinoc/mdl-docs/blob/master/docs/appen...


It was the early 80s. Readability wouldn't be invented for about another 20 years or so.

Actually kinda serious. You need to be able to spare the RAM to have the comments, whitespace, large variables, breaking things nicely into functions, etc. I've got modules I've documented where the documentation alone would fail to fit into, say, a Commodore 64's RAM, as plain text. Early 1980s "big iron" would still be considered on the high end of embedded programming today. (In the sense that a Raspberry Pi is embedded programming.)


I first learned programming in BASIC on an 8K PET. The 8K was shared by the source code, data, the executing program and display memory.

Spaces were for suckers. Each line had a few bytes overhead so you put as many statements on each line as you could. You stuck to 1 character for your common variables (only 2 characters were significant anyway). You used the abbreviated name for commands. (This was around 81, 82.)

Something like this:

> 10 FORT=1TO10:?"IHN!":NEXTT:FORT=1TO1000:NEXTT:GOTO10"

This is from memory, so probably not actually correct code but this is meant to print "IHN!" 10 times, wait about a second and then repeat. T is used as a FOR loop variable twice.


They developed these on a https://en.wikipedia.org/wiki/PDP-10. It's not like they were editing/compiling on the C64. Of course targeting small systems does influence the coding style, but...


A top-end PDP, according to that page, could address ~8MB, though I'm sure they didn't have it for a while; other charts on the internet suggest a top-end PDP-10 would get up to 33 mega Hz.

8MB for an embedded controller is no great ask and you have to crawl a ways down the spec sheet pile to get a 33MHz processor nowadays. A top-end PDP-10 would today put it roughly on par with an Arduino [1], although the Arduino would be a bit short on RAM.

Even if you have the space to spare on comments and long variable names, you've been raised in a culture that considers those insanely expensive extravagances.

[1]: https://www.arduino.cc/en/Products/Compare


Here's some code from the 1970s MIT Lisp/PDP-10 culture: https://github.com/dhess/rabbit-scheme/blob/master/rabbit.ls...

The indentation and all-caps are not modern, and the MACLISP primitive names like TERPRI are unchanged from the 60s. Otherwise, the coding and commenting style would not really raise any eyebrows today.

I first learned of literate programming from this culture instead of Knuth, e.g. https://dspace.mit.edu/handle/1721.1/41983 ("The fourth section presents a completely annotated interpreter for AMORD").

I'm just saying yeah, expectations have definitely evolved, but it's not the case that PDP-10 hackers back then had not yet discovered or valued readability.


Yes, of course some people had that opinion. It was a fringe position back then, though.

It's less fringe today, but I'm still not sure "code should be optimized for reading, not for writing" isn't still a fringe position, at least as evidenced by what people's actions say about their beliefs, rather than their words. Lip service of that is probably at least not fringe any more, which is progress of its own sort.


All I'm saying is that the Infocom Imps developed on a computer that was plenty big enough for comments and readable names to be no extravagance. The sources above show it happened in practice and not just in theory. The company started after they'd been used to this kind of environment for years. (And they were at least adjacent to actual literate-programming pioneers.)

I'm not saying they were into literate programming, I'm saying it's misleading here to talk about what coding on the C64 was like.


That's a really interesting perspective. If I think about it an application really doesn't need to be large or complex at all to accrue a couple hundred kB of source code, libraries not included.


COBOL is pretty readable.


deceptively readable


Angle brackets set out FORMs, parens set out LISTs. In English: FORMs are to function calls as LISTs are to list literals.

A translation from MDL to Python would look like this:

   <a b c>   ->   a(b, c)
   (a b c)   ->   [a, b, c]
(This is in contrast to a sibling comment: in MDL

   (<+ 1 2> <+ 3 4>)
would evaluate to

   (3 7)
)

However, there is a bit of a caveat in the translation. In MDL, atoms are self-evaluating, and it takes a call to LVAL or GVAL to obtain their stored values (as a shorthand, dot and comma prefixes give LVAL and GVAL calls, respectively). I haven't been able to find out from exactly which scope the evaluator gets the definition of a function. Which of the following two corresponds to what the evaluator actually does?

   <a b c>   ->   <.a b c>
   <a b c>   ->   <,a b c>


Function definitions normally go in the GVAL, so

  <DEFINE FOO () ...>
is equivalent to

  <SETG FOO <FUNCTION () ...>>
But EVAL will fall back to the LVAL if the atom doesn't have a GVAL. In other words:

  <A B C>
is equivalent to

  <APPLY <COND (<GASSIGNED? A> ,A)
               (<ASSIGNED? A> .A)
               (T <ERROR>)>
         B
         C>
https://github.com/taradinoc/mdl-docs/blob/master/docs/03-bu...


Thanks! (And thanks for having those docs up, they have been useful to me the past couple days.)

Another thing I couldn't find is exactly what a trailer with an empty OBLIST means. Does it use <ROOT>? It shows up in TELL for the uses of RETURN!-, which probably are to use a non-overridden version of RETURN.

(Or any idea what SETG20 is supposed to mean? It shows up in Bureaucracy.)


Yeah, if there's nothing after the final !- in an atom, it means the atom is in <ROOT>. (See section 15.5 in the manual.)

I'm not sure what the purpose of things like <RETURN!-> and <ORB!-> is. It could have something to do with the way they implemented Z-machine opcodes as MDL atoms, i.e. <RETURN!-> forces it to use the MDL SUBR instead of the opcode? Maybe they moved the MDL atoms that collided with Z-machine opcodes to a separate OBLIST, and this was needed to access them from MDL code in the same file?

I never figured that part out, so when ZILF is generating code, it uses the same OBLISTs it used while interpreting and just looks things up by their PNAMEs.

SETG20 and DEFINE20 are used in "MDL-ZIL" files, where routines are defined with DEFINE instead of ROUTINE, global variables are created with SETG instead of GLOBAL, etc. Presumably that was a way to run the games in MDL during development to avoid recompiling them. SETG20 and DEFINE20 are aliases for the MDL versions of SETG and DEFINE.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: