Jump to page: 1 2
Thread overview
REPL semantics
Jul 12, 2018
Luís Marques
Jul 12, 2018
Luís Marques
Jul 12, 2018
jmh530
Jul 12, 2018
Luís Marques
Jul 12, 2018
aliak
Jul 12, 2018
Luís Marques
Jul 13, 2018
jmh530
Jul 13, 2018
Jacob Carlborg
Jul 13, 2018
Luís Marques
Jul 13, 2018
Seb
Jul 13, 2018
Luís Marques
Jul 13, 2018
Jacob Carlborg
Jul 13, 2018
Luís Marques
Jul 16, 2018
aliak00
Jul 12, 2018
jmh530
Jul 12, 2018
Luís Marques
Jul 13, 2018
jmh530
Jul 12, 2018
Luís Marques
July 12, 2018
Consider a D REPL session like this:

> void bar(long x) { writeln(x); }

> void foo() { bar(42); }
42

> void bar(int) {}

Assuming implementation complexity is not an issue, what do you feel is the more natural semantics for a REPL? Should foo now call bar(int), or should it still call bar(long)? (feel free to generalize the issue)

I was curious to see what the existing REPLs did, but they seem to have bit rotted and no longer compile.
July 12, 2018
On Thursday, 12 July 2018 at 19:07:15 UTC, Luís Marques wrote:
> Assuming implementation complexity is not an issue, what do you feel is the more natural semantics for a REPL? Should foo now call bar(int), or should it still call bar(long)? (feel free to generalize the issue)

BTW, this succeeds:

    long foo(long x) { return x; }
    enum x = foo(42);
    mixin("int foo(int x) { return x-1; }");
    enum y = foo(7);
    static assert(x == 42);
    static assert(y == 6);

...but is it guaranteed by the spec? Alternatives: 1) implementation-defined ordering (of the mixin and the enums), possibly different than the existing frontend; 2) non-deterministic ordering, can change from one compiler run to another.
July 12, 2018
On Thursday, 12 July 2018 at 19:07:15 UTC, Luís Marques wrote:
> Consider a D REPL session like this:
>
>> void bar(long x) { writeln(x); }
>
>> void foo() { bar(42); }
> 42
>
>> void bar(int) {}
>
> Assuming implementation complexity is not an issue, what do you feel is the more natural semantics for a REPL? Should foo now call bar(int), or should it still call bar(long)? (feel free to generalize the issue)
>
> I was curious to see what the existing REPLs did, but they seem to have bit rotted and no longer compile.

Most REPLs I've used are for languages with dynamic typing. Perhaps take a look at a C REPL and see what it does?
July 12, 2018
On Thursday, 12 July 2018 at 20:33:04 UTC, jmh530 wrote:
> On Thursday, 12 July 2018 at 19:07:15 UTC, Luís Marques wrote:
> Most REPLs I've used are for languages with dynamic typing. Perhaps take a look at a C REPL and see what it does?

Well, cling calls the original function:

[cling]$ #import <stdio.h>
[cling]$ void foo(long x) { printf("long\n"); }
[cling]$ void bar() { foo(42); }
[cling]$ void foo(int x) { printf("int\n"); }
[cling]$ bar()
long

...but to me that doesn't mean much. If it was the other way around (bar was updated to call foo(int)) I think I could safely conclude that it was an intended consequence. But the actual behavior can easily be explained by the fact that that's the most straightforward implementation (especially for a REPL that uses an existing C++ frontend, like clang). I was looking for a more fundamental answer: what would the user prefer to happen?
July 12, 2018
On Thursday, 12 July 2018 at 21:15:46 UTC, Luís Marques wrote:
> On Thursday, 12 July 2018 at 20:33:04 UTC, jmh530 wrote:
>> On Thursday, 12 July 2018 at 19:07:15 UTC, Luís Marques wrote:
>> Most REPLs I've used are for languages with dynamic typing. Perhaps take a look at a C REPL and see what it does?
>
> Well, cling calls the original function:
>
> [cling]$ #import <stdio.h>
> [cling]$ void foo(long x) { printf("long\n"); }
> [cling]$ void bar() { foo(42); }
> [cling]$ void foo(int x) { printf("int\n"); }
> [cling]$ bar()
> long
>
> ...but to me that doesn't mean much. If it was the other way around (bar was updated to call foo(int)) I think I could safely conclude that it was an intended consequence. But the actual behavior can easily be explained by the fact that that's the most straightforward implementation (especially for a REPL that uses an existing C++ frontend, like clang). I was looking for a more fundamental answer: what would the user prefer to happen?

Cool, is there on going work to sprucing up the D repl in the dlang-community repo or is this a new attempt? Either way if something is happening here then awesome!

As for your question, hard to say me thinks. On the one hand, being able to do this is nice:

const int i = 3;
const int j = 4;
void complexCalculation() { use i and j }
complexCalculation() // uses 3 and 4
const int j = 5;
complexCalculation // uses the new j

On the other hand being able to redefine the name "j" as some other type to use in some other computation without having `complexCalculation` get messed up is also nice :)

Which is how the swift repl works:

  1> func f(_ a: Float) { print("f") }
  2> f(3)
f
  3> func f(_ a: Int) { print("i") }
  4> f(3)
i
  5> func foo(_ a: Float) { print("f") }
  6> func bar() { print(foo(3)) }
  7> bar()
f
  8> func foo(_ a: Int) { print("i") }
  9> bar()
f

For reference, this is from node and ruby but not with overloads since there's no function overloading: They use the current state of the source it seems.

> var i = 3;
undefined
> function f() { console.log(i); }
undefined
> f()
3
undefined
> var i = 4;
undefined
> f()
4
undefined

Ruby:

2.4.0 :002 > def f0() 3 end
 => :f0
2.4.0 :003 > f0()
 => 3
2.4.0 :004 > def f1() f0() end
 => :f1
2.4.0 :005 > f1()
 => 3
2.4.0 :006 > def f0() 4 end
 => :f0
2.4.0 :007 > f1()
 => 4
2.4.0 :008 >



Cheers,
- Ali



July 12, 2018
On Thursday, 12 July 2018 at 21:15:46 UTC, Luís Marques wrote:
> On Thursday, 12 July 2018 at 20:33:04 UTC, jmh530 wrote:
>> On Thursday, 12 July 2018 at 19:07:15 UTC, Luís Marques wrote:
>> Most REPLs I've used are for languages with dynamic typing. Perhaps take a look at a C REPL and see what it does?
>
> Well, cling calls the original function:
>
> [cling]$ #import <stdio.h>
> [cling]$ void foo(long x) { printf("long\n"); }
> [cling]$ void bar() { foo(42); }
> [cling]$ void foo(int x) { printf("int\n"); }
> [cling]$ bar()
> long
>
> ...but to me that doesn't mean much. If it was the other way around (bar was updated to call foo(int)) I think I could safely conclude that it was an intended consequence. But the actual behavior can easily be explained by the fact that that's the most straightforward implementation (especially for a REPL that uses an existing C++ frontend, like clang). I was looking for a more fundamental answer: what would the user prefer to happen?

I think most people, at least most people who have used REPLs before, would think that the above should print int. But this is because most REPLs are used with dynamic languages. I don't doubt that it makes sense that it is easier to implement such that it prints long. You're compiling each line as it comes in, so bar compiles to some machine code that can only depend on the definition of foo at the time it is compiled.

I think the mental model of someone coming from a dynamic language would be as if bar is dynamically re-compiled when the foo(int x) is entered.
July 12, 2018
On Thursday, 12 July 2018 at 21:51:18 UTC, aliak wrote:
> Cool, is there on going work to sprucing up the D repl in the dlang-community repo or is this a new attempt? Either way if something is happening here then awesome!

Ah, that explains why my clone of drepl didn't compile: it was the Martin Novak's repo, not the D community one. Although on macOS it still doesn't compile, because of the lack of _rt_loadLibrary.

Regarding your question: I was investigating this as part of my own D-related compiler efforts (DHDL stuff), but it won't materialize into a D repl anytime soon. I actually never tried the existing REPLs, what are your issues with them?

> As for your question, hard to say me thinks. On the one hand, being able to do this is nice:
>
> const int i = 3;
> const int j = 4;
> void complexCalculation() { use i and j }
> complexCalculation() // uses 3 and 4
> const int j = 5;
> complexCalculation // uses the new j
>
> On the other hand being able to redefine the name "j" as some other type to use in some other computation without having `complexCalculation` get messed up is also nice :)

I hadn't even considered *redefining* symbols, only overloading. cling doesn't support redefining. Mmmm...

> Which is how the swift repl works:
>
>   1> func f(_ a: Float) { print("f") }
>   2> f(3)
> f
>   3> func f(_ a: Int) { print("i") }
>   4> f(3)
> i
>   5> func foo(_ a: Float) { print("f") }
>   6> func bar() { print(foo(3)) }
>   7> bar()
> f
>   8> func foo(_ a: Int) { print("i") }
>   9> bar()
> f

Yeah, I had tried basically the same Swift example. But my point stands: I think that behavior can be explained by ease of implementation. Finding an example of the alternative would be much more interesting. Lacking that we are going to have to actually *think* about the problem ;-)

The examples with the dynamic languages are less relevant.
July 12, 2018
On Thursday, 12 July 2018 at 22:04:39 UTC, jmh530 wrote:
> I think the mental model of someone coming from a dynamic language would be as if bar is dynamically re-compiled when the foo(int x) is entered.

Right. Hopefully there aren't too many weird cases once that is generalized to other corners of the language. I also never used REPLs for major development, only for debugging and minor tests, so I don't have experience with that style of development where you code everything in the REPL and then save the whole state, which makes it harder for me to evaluate how important certain REPL features are.
July 12, 2018
On Thursday, 12 July 2018 at 19:07:15 UTC, Luís Marques wrote:
> Consider a D REPL session like this:

Unlike cling, drepl doesn't seem to support overloading:

Welcome to D REPL.
D> import std.stdio;
std
D> void bar(long) { writeln("long"); }
bar
D> void bar(int) { writeln("int"); }
bar
D> bar(42)
`_mod2.bar` at /tmp/drepl.adse9Q/_mod2.d(3) conflicts with `_mod1.bar` at /tmp/drepl.adse9Q/_mod1.d(3)
template `std.stdio.write` cannot deduce function from argument types `!()(void)`, candidates are:
/usr/include/dmd/phobos/std/stdio.d(3669):        `std.stdio.write(T...)(T args) if (!is(T[0] : File))`

It can envision the implementation that produces that result, and in terms of that implementation it makes sense, but ouch, that's not user friendly or in-line with regular D semantics.
July 13, 2018
On Thursday, 12 July 2018 at 22:24:19 UTC, Luís Marques wrote:
>
> Right. Hopefully there aren't too many weird cases once that is generalized to other corners of the language. I also never used REPLs for major development, only for debugging and minor tests, so I don't have experience with that style of development where you code everything in the REPL and then save the whole state, which makes it harder for me to evaluate how important certain REPL features are.

I primarily use REPL's for prototyping. It just makes some things much easier. So for instance, I can load up some functions and libraries and data and then run a bunch of different statistics models without needing to compile everything again. I can also plot everything as needed without needed to compile everything all over again.
« First   ‹ Prev
1 2