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

This is semi-related to one of the killer features of Eclipse that never really made it into any large-scale systems: the ability to run incomplete or broken code. The Eclipse Compiler for Java had a special feature that it could generate bytecode for nearly any file, including those that were utterly broken. It would mostly work, and you could incrementally work on unit tests alongside the code being developed.

It was honestly one of the most productive environments I ever worked in, and I'm somewhat sad nobody else has implemented this.



Agda (2) has a similar feature called holes. Very similar to Haskell’s `nothing` and Scala’s `???`. The difference is that because of the dependently typedness the compiler can sometimes even fill in the code for you based on symbols in scope.


Haskell has also had typed holes for several major versions now. Any underscore or name beginning with an underscore (to include values and types, unsure about kinds) gets an informative error message describing the type of the name, e.g.:

  Found hole `_' with type f (Free f b)
and relevant bindings, if applicable:

  Relevant bindings include
    >>= :: Free f a -> (a -> Free f b) -> Free f b
      (bound at holes.hs:28:3)
    f :: f (Free f a) (bound at holes.hs:29:8)
    g :: a -> Free f b (bound at holes.hs:29:14)
  In the first argument of `Free', namely `_'
Very useful for working your way out of a situation where the specific incantation to get to the right type isn't obvious.

Examples from [0].

[0] https://wiki.haskell.org/GHC/Typed_holes


Could this be used to build an editor for Haskell like the one for Hazel?


Isn’t this possible with any untyped language?

It does sound like a good feature though - very few languages have opt-out type checking. This is much better than opt-in IMO.


Hazel will also run incomplete programs around holes. Most untyped languages will just crash as soon as something is incomplete.


Laziness would be enough for this case, Haskell will happily run your programs around undefined


Yes and no. You need a universal way of saying "something that should exist here is missing"


ACPUL works well even with partially broken code keeping programs free from crashes and freezes. Some functions were broken for a long time, but this didn’t block progress allowing me to complete 90% of important features and fix them after 10 years. This has been verified over time in practice. I believe even 30-50% of a program can work opening up many new possibilities.


Haskell has something like this with -fdefer-type-errors: https://downloads.haskell.org/ghc/latest/docs/users_guide/ex...

    $ cat foo.hs
    something = to be done
    x = x + 1
    wat = x "¯\\_(ツ)_/¯"
    main = print "hi"
    
    
    $ ghc --make -O foo -fdefer-type-errors && echo sucesfuly compoiled && ./foo
    [1 of 1] Compiling Main             ( foo.hs, foo.o )
    
    foo.hs:2:13: warning: [-Wdeferred-out-of-scope-variables]
        Variable not in scope: to :: t1 -> t2 -> t
      |
    2 | something = to be done
      |             ^^
    
    foo.hs:2:16: warning: [-Wdeferred-out-of-scope-variables]
        Variable not in scope: be
      |
    2 | something = to be done
      |                ^^
    
    foo.hs:2:19: warning: [-Wdeferred-out-of-scope-variables]
        Variable not in scope: done
      |
    2 | something = to be done
      |                   ^^^^
    Linking foo ...
    sucesfuly compoiled
    "hi"
    $


Haskell also has typed holes (with a similar -fdefer-typed-holes) since 7.10. I've described it in slightly more detail here in a previous post in this thread [0].

Typed holes (but not the defer- option) have been enabled by default for some time now. They're an immediate go-to when scratching my head over types. I prefer them to the type error output, not only because they give better suggestions, but also because they can be named (_a, _conversionFunction, etc).

[0] https://news.ycombinator.com/item?id=42016584


I use IntelliJ now and I definitely miss this feature of Eclipse.


Why? What did you miss about it?

I'm asking as I prefer strict compilers that force me to handle all cases.


Generally, it's a very pragmatic thing like being able to quickly run something to make sure it's working but some other part of the code is temporarily broken because I'm currently changing things and don't care that that part is currently broken. In IntelliJ I have to stop doing what I'm currently thinking about and go over to that other part of the code and comment out some things or otherwise fix it up (usually in a way that won't be permanent because it's broken for a reason) before I can run the code I'm working on.

In an ideal world, the codebase would be modular and testable and all those good things but I work in a large enterprise dev team and some of the codebase is many (many) years old and it's no longer feasible to refactor it into anything like what would be needed to allow the code to be modularized in such a way that would obviate the necessity to do the above.


I don't think they want to have thing broken in the steady state, but anything that relies on things building for analysis etc.,. Could benefit from this.


And then you have Go, which won't even let you compile code with an unused variable...


    func TestWhatever(t *testing.T) {
        // ...lots of code

        _, _, _, _, _, _, _ = resp3, resp4, fooBefore, subFoo, bar2, barNew, zap2
    }
Like, I get it, it's a good feature, it caught quite a lot of typos in my code but can I please get an option to turn this checking off e.g. in unit tests? I just want to yank some APIs, look at their behaviour, and tinker a bit with the data.


This example isn't particularly good code. If you've got "lots of code" that names a bunch of variables (e.g. using ':=') that are never referenced AND you have a good reason not to do so (which I doubt: given this context it looks like an incomplete test), then predeclare these 'excess' variables:

   func TestWhatever(t *testing.T) {
        var resp3, resp4, fooBefore, subFoo, bar2, barNew, zap2 theirType

        // ...lots of code
   }
Alternatively, use '_' where they are being defined:

   // instead of
   resp2, err := doit()

   // use
   _, err := doit()
If, and given this context it's likely, you're checking these errors with asserts, then either change the name of the error variable, predeclare the err name (`var err error`), or split it into multiple tests instead of one giant spaghetti super test.

That said, in a code review, at a minimum, I would probably ask that these variables be checked for nil/default which would completely eliminate this problem.


This is not a piece of code I would commit, obviously! It's a piece of code in the middle of being written and re-written (and re-run, a la REPL), and constantly replacing "resp2" with "_" and back again with "resp2" is friction. Go doesn't have REPL but having a TestWhatever(t *testing.T) function is a mostly good enough replacement, except for this one small problem.


Whew, that's a relief! If I understand correctly, then I think you'll have a better experience if you practice doing something like this when writing tests:

  foo, fooErr := doit()
  require.NotNil(foo)
  require.NoError(fooErr)
  _, _ = foo, fooErr // alternative if you don't want the asserts for some reason, remember to come back later and delete though

  // ...repl churn code... 
Using the stretchr/testify/require package. This code defines both variables, gives the error a unique name in the scope, and then references the names in two asserts. You won't have to deal with unreferenced name errors in the "repl churn", so you can comment/uncomment code as you go.


The good news is if you use Nix or Guix it’s relatively easy to hack your local build of the compiler to demote the unused variables hard error to just a warning.


You know, for some weird reason, it never crossed my mind to hack the Go compiler to let me do things like that. And it's such a great idea.



Well, it's about as easy without neither Nix not Guix.


It's really not. Yes it's possible to figure out how to build and install the Go compiler, but then you have to repeat that process every time you want to upgrade to a new version. With Guix (I assume Nix is similar) you just save the patch somewhere and then run `guix install --with-patches=go=/path/to/patch go` and everything just works (including reapplying the patch on upgrade).


All of this could've been prevented if Go just had two ways to compile. Debug and release.

The go devs decided against this since they didn't want to build a highly optimizing (read: slow) compiler, but that is missing the point of developer ergonomics.


It could be prevented in an even simpler way: emitting warnings.

Most people nowadays ban building with warnings in CI and allow them during local development. But “CI” was barely a thing when go was developed so they just banned them completely. And now they are probably too stubborn to change.


The Go decision was explicitly to not have warnings, and the unused identifier thing complained about is merely a consequence of that.

https://go.dev/doc/faq#unused_variables_and_imports


As an outsider to Go, it feels to me like this basic pattern comes up over and over again in Go:

Q. Why can’t I have feature X?

A. We thought of that already, and your opinion is wrong.

Q. But basically every other language in existence supports this. And it makes development easier. We would really, really like it. Please?

A. Apparently you don’t get it. Here’s a pointer to a 15 year-old post on a mailing list where the first time someone asked for this, we said no. Your opinion is wrong.


> And now they are probably too stubborn to change.

Sounds like we agree!


I have never heard about this before. What exactly would happen to broken code? For example, would it skip the equivalent of the broken source line, or would it stub out a function altogether or what?


I only used that feature inadvertently a long, long time ago. As I remember, the program would throw a Throwable exception when it would enter code that wasn't translatable. There was some sort of hot reloading, too. So you could try to fix the code and continue.

The really neat thing was that the Ecliose Java compiler is built specifically to support the IDE, so all the the warning and error annotations in the editor come from the actual compiler even while you are typing. There is no separate parser and linter just for the editor. I believe that the ability to translate broken source files on a best effort basis is actually an offshoot from that functionality.


Common Lisp's REPL works like this.


That sounds like an incredibly useful feature. Do you recall what version you were using?


I believe that Eclipse 2.x already had most of these features, but it certainly was in almost all 3.x versions as far as I remember. That IDE was amazingly far ahead of its time. Even 20 years later, tools like VS Code feel like a shocking regression in capabilities to me.


Well, regression in that feature set, but it’s better in other features, many of which drove people off of Eclipse.

When it worked, it was really, really good, agree. My experience was that it usually didn’t though, swap branches a few times and the caches would be broken, time for “invalidate caches and restart”. Multiple times per week, each time it’d take an hour to re-index.. that was a lot of time we got back again when we switched to IntelliJ


This sounds like the sort of feature that will show up in a "vintage software" youtube video essay in a few years. I kinda want to go find it and give it a whirl.


Literally that, it would throw exceptions with the compiler error. And as a sibling comment mentioned and I had forgotten -- it would allow for hotpatching code at runtime as you fixed compiler errors.

You could literally start the skeleton of a webserver and gradually add functionality to it without recompiling and it would mostly "just work". Certain changes would require the app to be restarted.


CRTL-SHIFT-F9 (IntelliJ) works for me on most Java and Kotlin code in IntelliJ, as long as no method/class signatures are changed.


embedded agile mode




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

Search: