intellij-haskell
intellij-haskell copied to clipboard
Case split feature
One thing I really miss from, say, Idris is case splitting a variable in a function definition or a case
expression.
For instance, having a type like
data FooType = Foo | Bar Int
and having written
foo :: FooType -> Smth
foo x = undefined
doing the suggested "case split" on x
should result in foo x = undefined
replaced with the corresponding patterns like
foo :: FooType -> Smth
foo Foo = undefined
foo (Bar n) = undefined
Doing this on x
in case x
context should behave similarly.
Being able to add missing clauses would also be super cool!
I know this request sounds a bit vague, so please let me know if you'd like me to elaborate!
Thanks for reporting!
I need to investigate this but I guess itβs doable for simple cases. The plugin lexer/parser is limited in recognizing Haskell syntax especially over multiple lines (I still need to continue with the branch which could solve this issue)
Are there more use cases you have in mind?
I would be very hyped if this were included, this and automatic type insertion are the two things I miss the most. And I do mean miss - ghc-mod was able to do that, while seemingly every other tool that came after it was not.
The use cases are probably just "whenever you want to pattern match", without this feature I am forced to lookup the constructors, and even if I remember them I have to type them out - even though GHC knows exactly what should be there when my pattern match isn't exhaustive yet - some keyboard shortcut while hovering over a sum type would be a real timesaver.
While we're at it, in theory a similar argument could be made for filling in dummy typeclass methods in instance declarations, albeit this is probably not necessary as often.
@mbwgh Thanks for your suggestion.
I once made support for typed holes but i noticed that's currently broken because the GHC message is changed. The idea was to type _
and then use IntelliJ's smart completion option to show the suggestions of GHC. Will fix that.
@mbwgh
automatic type insertion are the two things I miss the most.
Can you elaborate automatic type insertion? In this context i understand automatically adding of data constructors.
Sure thing. What I mean is simply being able to automatically (i.e. by using some key combination) insert the inferred type of a selected expression. Some examples:
someTopLevelConstant = 42
If I have the cursor over this top level definition, I would like to be able to add the type signature (Integer
or Num a => a
depending on whether the monomorphism restriction is active) inferred by ghc. This would ideally also be a quick-fix since there would be a warning.
The harder cases would be adding the types for type-level holes like
someIOAction :: IO _
someIOAction = liftIO getLine
Something like this could occur when you have some highly polymorphic type and you want to hint at a simplified signature.
Here, the IO
would constrain the type of the monad, without it ghc would infer MonadIO m => m String
.
Then there's of course adding type signatures to expression-level typed holes.
One more thing where it would be really nice to have is in situations like this:
process :: Num a => [(a, String)] -> Bool
process = foldl' go False
where
go = undefined
where you intend to fold over some structure and can't remember the type of the function argument.
This one is probably more tricky because there is a difference if ScopedTypeVariables
is enabled or not.
Still, right now I have to type out the type annotation for go
by hand, and if I want to query the type I have to hover over the go
in the foldl' go False
expression, since the hole in go = _
would be typed something like a
.
There are probably more examples and maybe even better ones, but hopefully this conveys the intention. Thanks for taking this into consideration :)
If I have the cursor over this top level definition, I would like to be able to add the type signature (Integer or Num a => a depending on whether the monomorphism restriction is active) inferred by ghc. This would ideally also be a quick-fix since there would be a warning.
That's already implemented but it depends on GHC messages. So you have to enable e.g. -Wall
.
That's already implemented but it depends on GHC messages. So you have to enable e.g. -Wall .
I will add default GHC options to the REPL soon so those quick fixes/intentions are by default enabled.
I would say that the quickfix doesn't cover every use case I mentioned, so it would probably not allow me to annotate a let
binding. Maybe indirectly via a typed hole, though.
In any case, thanks for letting me know about the quickfix that is already there.
Maybe I should investigate this in a little more detail and open a feature request if there really is some functionality missing, since obviously this is getting a little off-topic here.
I would say that the quickfix doesn't cover every use case I mentioned,
Yes, it currently only works for top-level expressions.
Maybe I should investigate this in a little more detail and open a feature request if there really is some functionality missing, since obviously this is getting a little off-topic here.
π
Btw, the next version of the plugin has improved support for code completion on typed holes (also on type level)
Making some progress in implementing in case splitting
Yay @rikvdkleij this looks awesome!
@0xd34df00d
Doing this on x in case x context should behave similarly.
Can you elaborate on this?
I see 2 solutions:
testcase :: FooType -> String
testcase m = case m of
x -> undefined
or
testcase2 :: FooType -> String
testcase2 = _
The first one on the x
. For this solution x -> undefined
needs to be on new line to make it more simple to implement.
The second one does generate the whole case expression within lambda, is it bit more complex to implement but I think more practical and solid.
@rikvdkleij what I had in mind is the first option β just having an arbitrary case
, not necessarily the top-level term on the RHS of a function. For instance, I can easily think of foo x = bar $ case x of ...
, where case-splitting on x
is still desirable. Or it could even be some complex expression like case foo (bar baz) meh of ...
, but I'm not sure if you want to support that, cause it might require some non-trivial type inference in the general case.
Anyway I think it's your first solution.
And even if it requires me to format the code in a certain way, then I'm surely fine with it.
BTW idris' editor integration (that gave me inspiration for this request) has two separate commands β one for generating a case expression (akin to your second solution, but it leaves a hole for the expression to case-split on), and another one for case-splitting on any variable (so you'd place the cursor on x
in x -> undefined
and issue that command). Not sure if it's the best way to implement this, but just some relevant prior art.
@0xd34df00d Implemented in beta79. I'm curious if it works for you π
@mbwgh In beta79 I have added default GHC options to REPL (which can be changed in Haskell Settings)
@0xd34df00d Improved top-level case splitting support in beta80.
@rikvdkleij thanks! Just got my hands on trying this out β works like a charm for a few limited examples I've tried! Thanks again for implementing this!
@0xd34df00d Thanks for letting me know! Great to hear that it works for you.
Probably it will not work for all cases. You can always report those. I will keep improving the plugin.