14 comments

[ 2.8 ms ] story [ 40.7 ms ] thread
This seems like just another tool to add to my tool box. Maybe it helps someone
I find Haskell is really good for this. Typed holes + ghcid makes for a really fast and helpful feedback loop.
Ghci is a really impressive REPL, with very fast reloading and very complete metadata handling. Also, it's easier to handle conversations about pure code.

But most languages have many features aimed at it. The captcha is that it tends to come bundled on the debugger, instead of in a separate tool. That makes many people fail to recognize them.

what is typed holes?
https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type... <- This does a better job than I could at explaining it. But basically, certain expressions are "holes", incomplete/invalid code but where the type of the hole (a variable or _) can be determined. The compiler can also give suggestions on how to fill a hole.

I learned about them with Idris and Type-Driven Development by Brady, I've never used them in Haskell but haven't really touched it seriously in a decade now.

    ghci> let x :: Int = 1; s :: String = "foo" in x + _

    <interactive>:13:46: error:
 • Found hole: _ :: Int
 • In the second argument of ‘(+)’, namely ‘_’
   In the expression: x + _
   In the expression:
     let
       x :: Int = 1
       s :: String = "foo"
     in x + _
 • Relevant bindings include
     x :: Int (bound at <interactive>:13:5)
     s :: String (bound at <interactive>:13:19)
     it :: Int (bound at <interactive>:13:1)
   Valid hole fits include
     it :: Int (defined at <interactive>:11:1)
     x :: Int (bound at <interactive>:13:5)
     maxBound :: forall a. Bounded a => a
       with maxBound @Int
       (imported from ‘Prelude’ (and originally defined in ‘GHC.Enum’))
     minBound :: forall a. Bounded a => a
       with minBound @Int
       (imported from ‘Prelude’ (and originally defined in ‘GHC.Enum’))
@ParetoOptimal this is helpful in that haskell/ghc throws these error but how do you read the error message and how do you use it or infer next steps from the messages regarding the typed hole?
> @ParetoOptimal this is helpful in that haskell/ghc throws these error but how do you read the error message and how do you use it or infer next steps from the messages regarding the typed hole?

Basically it gives you a good starting point at least for what things make sense there. In some contexts though, it can lead you to the right answer when you didn't really have a clear idea provided you've honed some heuristics.

In the example above I:

1. Look at the type from this piece of the error: `Found hole: _ :: Int`. 2. I look at relevant hole fits, user defined functions in scope, and see if any of those belong from the `Relevant bindings` part of the error. 3. If not, I look to `Valid hole fits include` to find valid functions that may fit.

In step 3 however, there's a bit of pattern recognition because very polymorphic functions that never make any sense can sometimes show up there. `minBound` and `maxBound` fit for instance. Other things show up here that you can usually filter out as well such as `forever` when using holes in monad functions.

The above example was for the case where you have a high degree of confidence that there are no other errors. In general, type driven development hinges upon making one small change at a time and having only one type error at a time in your code.

That isn't as restrictive as it seems however, because you can always replace a problematic expression or function full of said expressions with (undefined :: TypeIWishThisExpressionRepresented).

Fast feedback loops!

In my Fast Development workshop I recommend:

Goals are speed (low latency developer feedback), and quality (create something the client expects and values).

Speed: use `entr` or another tool to automatically run tests when a file changes.

Quality: "test" first -- write a test before code, if you have to think about the code. In this way you get high-level dev feedback, before the code is written.

Quality: use the system to calculate your function's result. Then either use "assert 0, value" to emit the result to you the developer, or use the debugger. In either case the computer does the work and you get feedback.

The above three-part process is similar to the OP, "conversational" is a good term for it.

Where might someone find this Fast Development workshop?
Could use an example of how this workflow is superior to having a debugger drop in repl like you can do in many languages. For example pry for ruby or debugger in chrome, which allow you to interact with the code in it’s current state. With lisp i think the benefit is you can evaluate as you go and skip debugging all together.
I'm the author, I think both. I'm working on DAP (https://microsoft.github.io/debug-adapter-protocol/) support for Clojure to use alongside Conjure connected to the same running Clojure program.

I can get by without a debugger but I think both have their strengths and I want both. Actually I want all three: REPL, LSP, DAP. The REPL layer gets to be super small and simple when LSP and DAP are handling the other harder parts of IDE-ish features.

Don't pick one, have your cake and eat it :) future post I'm planning talks about this sort of trifecta and all of my favourite editor things coming together alongside fast auto-running unit tests.