Live data from Hacker News

What makes a good REPL?

vvvvalvalval.github.io

151–160 of 177 posts

Re: What makes a good REPL?

#151
post #114

Take a look at Smalltalk's environment and take a look a t Common Lisp's REPL. They have all the features that make for a good 'REPL'. (As noted before, REPL is a Lisp term that stands for: read - from keyboard input, parse the input string into the syntactic structure of the language eval - eval the expression, this includes binding variables or defining new functions, also re-defining functions, even if such functi…

> in Lisp all expressions evaluate to something Minor nitpick, but note that if you define: (defun foo () (values)) Then (foo) does not return a value and accordingly, the REPL prints nothing. But in a context where you need a value, that value would be NIL: if A evaluates to 3, then after (setf a (foo)) it will evaluate to NIL.

According to a famous epigram by Alan Perlis, Lisp programmers know "the value of everything, but the cost of nothing". It reflects the expectation that a language which calls itself Lisp is expected to produce a value out of any expression which evaluates.

Though Common Lisp adds the nuance of multiple values, the behavior you describe is how it conforms to this general expectation. Code written in an everything-really-has-one-value dialect of Lisp can be easily transported to Common Lisp (or at least transported without without difficulties specifically caused by this issue).

Scheme, a Lisp-like language, allows some evaluable expressions to have an "undefined" or "unspecified" result value. Logic translated to Scheme from a Lisp dialect without attention to this issue can have a surprising or incorrect behavior. For instance, if the original code executes a do loop, with the expectation that it yields nil (or some similar false/empty value in the original dialect). In Scheme's do loop, if the result expression is present then it specifies the value; otherwise the value is not specified.

Re: What makes a good REPL?

#152
post #62

I am not convinced that immutability matters; this seems like a bias. After all the original REPL, and the name itself, was in Lisp (note that the first Lisp implementations were not interactive, but it was the first interactive language) and Lisp doesn't have immutable data structures. (READ, EVAL, and PRINT are all old Lisp primitives, and the REPL was literally implemented with them. There's also a complex macro c…

[deleted]

Re: What makes a good REPL?

#153
post #91

Earlier quoted context omitted.

I'd even say immutability can be a disadvantage. I normally only use a repl when I don't know what I'm doing. My normal process with a repl in an immutable language goes like this: 1. Assign something to a name 2. Realise that was wrong, try again. 3. Get told no. Remember to tell the repl to forget the wrong one and do it again (or choose a new name). 4. Try to assign something else to another name (probably 'foo')…

I think there may be some confusion here, immutability is about the stability of values (e.g. I have a string, list, map etc and I want to pass it to others, view its value, make changes without affecting the original value). Immutability is not the same as rebinding a name (in clojure at least) e.g. the following is perfectly valid (global and local binding of the same name repeatedly) (def a 1) (def a 2) (def a 3)…

For a global def to be a "rebinding" rather than assignment is crippling. It means that previous references to a still see the old thing, which is wrong if you want them to see the new thing.

Re: What makes a good REPL?

#154
post #146

Earlier quoted context omitted.

> I have a feeling given your screen name I'm talking to some who is equally biased as I am to Java :) The main difference: I have a Lisp Machine at home. :-) > Hmmm an IDE is supposed to be a REPL and more. No, a Read Eval Print Loop came from Lisp in the early 60s. It originally means to read a data structure, treat it as code and evaluate it and print the result data structure. READ, EVAL, PRINT are actual functio…

I think I'm in agreement with you. What I meant by the IDE is that "ideally" it should have REPL like offerings if the language can support hot code swapping. I still don't think its REPL that makes Lisp or clojure magic (when I say magic I mean awesome). Its all the other stuff like macros and homoiconicity (which I see your point plays some part in academic REPL). > Even JRebel can not do to a running JVM applicati…

> Well thats because of the Java compiler and in some parts the language of Java. It has nothing to do with the JVM otherwise Clojure wouldn't work.

Clojure is constrained by the JVM and its implementation. Though Java is even more constrained.

You get a mini Common Lisp Object System demo in the LispWorks REPL:

We define a class person with a slot 'name':

  CL-USER 1 > (defclass person () ((name :initarg :name :accessor name)))
  #
Let's create a list of persons:

  CL-USER 2 > (setf persons (mapcar (lambda (name)
                                      (make-instance 'person :name name))
                                    '("Jan" "Ralph" "Joan")))
  (# # #)
Let's define a custom print method:

  CL-USER 3 > (defmethod print-object ((p person) stream)
                (print-unreadable-object (p stream :type t :identity t)
                  (write-string (name p) stream)))
  #

How does a person print now?

  CL-USER 4 > persons
  (# # #)

Let's add a slot to the class, a slot 'age':

  CL-USER 5 > (defclass person ()
                ((name :initarg :name :accessor name)
                 (age  :initarg :age  :accessor age :initform 0)))
  #
Let's update the print method:

  CL-USER 6 > (defmethod print-object ((p person) stream)
                (print-unreadable-object (p stream :type t :identity t)
                  (format stream "~a ~a" (name p) (age p))))
  #
Woops: all persons now have already got the new slot:

  CL-USER 7 > persons
  (# # #)
Let's set the new slot:

  CL-USER 8 > (mapc (lambda (p age)
                      (setf (age p) age))
                    persons
                    '(23 43 21))
  (# # #)

Let's define a new class: social-security-mixin:

  CL-USER 9 > (defclass social-security-mixin ()
                ((social-security-number :initarg :ssn :accessor ssn)))
  #

Let's add this new class to the superclasses of PERSON.

  CL-USER 10 > (defclass person (social-security-mixin)
                ((name :initarg :name :accessor name)
                 (age  :initarg :age  :accessor age :initform 0)))
  #
Now we do something really wild: we write an around method for printing:

  CL-USER 11 > (defmethod print-object :around ((p person) stream)
                 (print-unreadable-object (p stream :type t :identity t)
                   (call-next-method)))
  #
We redefine the original method just to print the name and age of the person.

  CL-USER 12 > (defmethod print-object ((p person) stream)
                 (format stream "~a ~a" (name p) (age p)))
  #
Then we define an AFTER method for the social-security-mixin class:

  CL-USER 13 > (defmethod print-object :after ((o social-security-mixin) stream)
                 (format stream " ~a" (ssn o)))
  #
Now we set the social security number of the persons. Wait? Lisp has updated my objects, since I added a new superclass to their class? All objects now have a changed superclass for their class? They inherit the new slot?

And the print-method gets reassembled for the new inheritance tree and the changed set of methods?

  CL-USER 14 > (mapc (lambda (p ssn)
                      (setf (ssn p) ssn))
                    persons
                    '("123-345" "321-455" "443-222"))
  (# # #)
As you see the objects have a SSN and the print methods are dynamically combined. For the person it runs the around method, then the primary method of person and then the after method of the mixin. If I'd now change the inheritance tree, then the methods would be recombined according to the inheritance at runtime... I could also dispatch on the second argument...

CLOS supports multi-dispatch over multiple-inheritance with dynamic combinations of applicable methods.

CLOS can do quite a bit more than that...

Java can't do anything like that.

It can't update objects on class changes/inheritance changes/...

It can't combine methods based on the multiple-inheritance class tree.

It can't change the class of objects. It can't reprogram the object system itself. See the CLOS MOP...

Re: What makes a good REPL?

#155

Earlier quoted context omitted.

If you want to be literal about it then sure, it just means read-eval-print loop. But I think that's akin to saying that a functional programming language is a language that has functions in it. EDIT: To be clear, what I'm saying is that when people say 'I really love using Common Lisp because it has a REPL' they aren't saying 'I really love using Common Lisp because it has a prompt I can write raw strings of code in…

Sorry if my comment with the definition of REPL seemed "viscerally negative". I understand that a good REPL has more features that just the bare bones, but you said that programs that read an input, evaluated it, and then printed the result aren't REPLs. You should've said that they aren't good or useful REPLs. Your argument is the equivalent of saying notepad isn't a text editor because you can't edit multiple lines…

They aren't really REPLs.

>Your argument is the equivalent of saying notepad isn't a text editor because you can't edit multiple lines at once or highlight syntax.

No it's the equivalent of saying that not even programme that can possibly, technically edit a text file is a text editor.

Python's shell thing is not a REPL.

Re: What makes a good REPL?

#156
post #154

Earlier quoted context omitted.

I think I'm in agreement with you. What I meant by the IDE is that "ideally" it should have REPL like offerings if the language can support hot code swapping. I still don't think its REPL that makes Lisp or clojure magic (when I say magic I mean awesome). Its all the other stuff like macros and homoiconicity (which I see your point plays some part in academic REPL). > Even JRebel can not do to a running JVM applicati…

> Well thats because of the Java compiler and in some parts the language of Java. It has nothing to do with the JVM otherwise Clojure wouldn't work. Clojure is constrained by the JVM and its implementation. Though Java is even more constrained. You get a mini Common Lisp Object System demo in the LispWorks REPL: We define a class person with a slot 'name': CL-USER 1 > (defclass person () ((name :initarg :name :access…

Yes CLOS is superior particularly multimethods.

As for the JVM: https://common-lisp.net/project/armedbear/

Sooo its not the JVM.

BTW AspectJ and JRebel will get you around methods and even inheritance changes but alas Java does not have multimethods or MOP. I mean CLOS is awesome but so is static analysis :)

Re: What makes a good REPL?

#157
post #135

Earlier quoted context omitted.

> Lisp doesn't have immutable data structures Racket does have immutable data structures and Dr. Racket has a good REPL IMHO. http://beautifulracket.com/explainer/data-structures.html

That's the converse of my point. The article claimed immutable data is important for a good REPL and I said that the claim was an overstatement. I didn't say that immutable data makes a REPL impossible. I quite like immutable datastructures, as it happens.

I was replying that LISP doesn't have immutable data structures.

Re: What makes a good REPL?

#158
post #32

Matlab has a nice sort-of-repl feature which I miss in every other language: you can separate the code in a file into several blocks and then execute the current block (the one with which contained the cursor) with ctrl+enter. With this feature you still have the full text editing capabilities but you also have a flexibility you get from a repl.

Most Lisp modes for Emacs have an eval-sexpr-at-point command which allows you to send the current sexpr to the REPL. This is in SLIME for CL but even the most basic Scheme mode has it as well.

Emacs can do this with Ruby, Python... Hell, you can get it to eval buffer, region, line with anything that'll accept input and return a useful output.

The ease at which one can get any language (or I/O machine) to play along with this workflow in Emacs is astonishing.

Re: What makes a good REPL?

#159
post #154

Earlier quoted context omitted.

> Well thats because of the Java compiler and in some parts the language of Java. It has nothing to do with the JVM otherwise Clojure wouldn't work. Clojure is constrained by the JVM and its implementation. Though Java is even more constrained. You get a mini Common Lisp Object System demo in the LispWorks REPL: We define a class person with a slot 'name': CL-USER 1 > (defclass person () ((name :initarg :name :access…

Yes CLOS is superior particularly multimethods. As for the JVM: https://common-lisp.net/project/armedbear/ Sooo its not the JVM. BTW AspectJ and JRebel will get you around methods and even inheritance changes but alas Java does not have multimethods or MOP. I mean CLOS is awesome but so is static analysis :)

ABCL implements CLOS classes in Java. It does not use the Java/JVM directly. For example a CLOS class is an instance of some Java class. This instance then has an attribute which has a vector of the CLOS slots. CLOS slots are not Java attributes themselves... The JVM object model is simply not able to provide CLOS features directly.

Last I've looked Jrebel used a funny mechanism. One couldn't just tell the class to add a slot, but one has to have Jrebel installed and given a new class file, it will detect it and then change/load the class...

That's a rather limited mechanism aimed at development... especially since it needs a license to work...

Re: What makes a good REPL?

#160

Earlier quoted context omitted.

I think there may be some confusion here, immutability is about the stability of values (e.g. I have a string, list, map etc and I want to pass it to others, view its value, make changes without affecting the original value). Immutability is not the same as rebinding a name (in clojure at least) e.g. the following is perfectly valid (global and local binding of the same name repeatedly) (def a 1) (def a 2) (def a 3)…

For a global def to be a "rebinding" rather than assignment is crippling. It means that previous references to a still see the old thing, which is wrong if you want them to see the new thing.

If you want others to see the new thing automatically, you need something much more powerful than plain old assignment anyway: something which will let you pick a synchronization strategy.

"Changes at any arbitrary time, including when you are halfway through reading it" is not in any way a sound synchronization strategy and the only thing you get with assignment.

You seem to understand the important difference between identity (what you see when you read an object) and reference (what you use to access the object). Next important thing on the list is how "reference" cannot just be a pointer to a place in memory -- unless it is immutable.

Post reply on HN