Using a REPL vs. an IDE like you describe is the difference between a conversation and sending somebody a letter with instructions.
This is more visible when we use more dynamic languages/runtimes than Java/JVM. Since the changes one can do and how they need to be done is not very advanced, the usefulness of a REPL is reduced.
> Using a REPL vs. an IDE like you describe is the difference between a conversation and sending somebody a letter with instructions.
Hmmm an IDE is supposed to be a REPL and more. I mean you can go look up the definition from wikipedia.
> This is more visible when we use more dynamic languages/runtimes than Java/JVM. Since the changes one can do and how they need to be done is not very advanced, the usefulness of a REPL is reduced.
Yes I completely agree as I mentioned dynamic languages are far easier to modify at runtime. However for the case with Java it can be done with JRebel and various other tools.
Furthermore going back to the whole conversation vs letter an IDE with a powerful debugger will let you evaluate expressions based on a state that is stuck... ie setting breakpoint (as well of course as investigating current variables and such). This is damn useful for dealing with a multithreaded environment.
By the way make no mistake... I do love Lisp... I just think there are better things than traditional REPLs considering to your other point in another thread this stuff has existed since the 70s.
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 functions in Lisp. This stuff executes in a LOOP and is enriched by all kinds of stuff.
An IDE does not need to have a REPL. If it can interact with a running application (for example via a debugger), this might still not be a REPL.
> However for the case with Java it can be done with JRebel and various other tools.
Even JRebel can not do to a running JVM application what some Lisp implementations can do. Not near of that.
> IDE with a powerful debugger will let you evaluate expressions based on a state that is stuck... ie setting breakpoint (as well of course as investigating current variables and such)
This is pretty basic.
> traditional REPLs
Check out Symbolics Dynamic Windows and McCLIM on the Lisp side...
Old demos from me:
https://www.youtube.com/watch?v=VU_ELJjbnWM
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 application what some Lisp implementations can do. Not near of that.
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. But I agree JRebel is far cry from the full reloading capabilities of Lisp, Erlang and other dynamic languages.
> IDE with a powerful debugger will let you evaluate expressions based on a state that is stuck... ie setting breakpoint (as well of course as investigating current variables and such)
> This is pretty basic
I agree but its still surprising how many languages do not do this well and I didn't mention that you can execute simple expressions in that mode something other static languages like C will not allow.
Besides.... I can change a function name in Java or Scala and see immediately everywhere in my code base with (e.g. red squiggle lines) how that impacts other code... for static languages that is pretty basic :P
I'm totally envious of your lisp machine (EDIT: in all honesty...I realize that originally sounded sarcastic).
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)))
#<STANDARD-CLASS PERSON 402005BA03>
Let's create a list of persons: CL-USER 2 > (setf persons (mapcar (lambda (name)
(make-instance 'person :name name))
'("Jan" "Ralph" "Joan")))
(#<PERSON 40200A493B> #<PERSON 40200A49F3> #<PERSON 40200A4AAB>)
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)))
#<STANDARD-METHOD PRINT-OBJECT NIL (PERSON T) 40200A942B>
How does a person print now? CL-USER 4 > persons
(#<PERSON Jan 40200A493B> #<PERSON Ralph 40200A49F3> #<PERSON Joan 40200A4AAB>)
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)))
#<STANDARD-CLASS PERSON 41404737D3>
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))))
#<STANDARD-METHOD PRINT-OBJECT NIL (PERSON T) 40201289FB>
Woops: all persons now have already got the new slot: CL-USER 7 > persons
(#<PERSON Jan 0 4140473733> #<PERSON Ralph 0 4140473933> #<PERSON Joan 0 4140473D3B>)
Let's set the new slot: CL-USER 8 > (mapc (lambda (p age)
(setf (age p) age))
persons
'(23 43 21))
(#<PERSON Jan 23 4140473733> #<PERSON Ralph 43 4140473933> #<PERSON Joan 21 4140473D3B>)
Let's define a new class: social-security-mixin: CL-USER 9 > (defclass social-security-mixin ()
((social-security-number :initarg :ssn :accessor ssn)))
#<STANDARD-CLASS SOCIAL-SECURITY-MIXIN 40200111C3>
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)))
#<STANDARD-CLASS PERSON 41404737D3>
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)))
#<STANDARD-METHOD PRINT-OBJECT (:AROUND) (PERSON T) 40200AB8A3>
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)))
#<STANDARD-METHOD PRINT-OBJECT NIL (PERSON T) 40200AC9EB>
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)))
#<STANDARD-METHOD PRINT-OBJECT (:AFTER) (SOCIAL-SECURITY-MIXIN T) 402001917B>
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"))
(#<PERSON Jan 23 123-345 4140473733> #<PERSON Ralph 43 321-455 4140473933> #<PERSON Joan 21 443-222 4140473D3B>)
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...