haskell / haskell/haskell-language-server

Split a selected variable or underscore pattern

Open
#5,075 2 comments 0 reactions 0 assignees View on GitHub
component: hls-case-split-plugin Hackathon type: enhancement
Dominant language
Haskell
Stars
3k
Forks
455
Avg merge
4d 3h
Merged PRs (30d)
12

Description

This is a suggestion to extend the case split plugin (which I'm very excited about) to support splitting a hovered variable or underscore pattern (which could be nested in a larger pattern). This could be applied in both nested and multi-pattern scenarios e.g. function definitions (top-level, let, where) and \cases - the LHS and RHS would be copied wholesale (apart from the pattern which is split). So it relates to #5068 but doesn't fully address it (e.g. the case of an existing incomplete function, or introducing lambdas/`\case`[`s`] to introduce the variables to split on).

Example - splitting `_` inside `Just _` on the first line here:
```Haskell
fun :: String -> Maybe Bool -> IO ()
fun (x:xs) (Just _) = putStrLn "example"
fun _ _ = pure ()
```
would produce
```Haskell
fun :: String -> Maybe Bool -> IO ()
fun (x:xs) (Just False) = putStrLn "example"
fun (x:xs) (Just True) = putStrLn "example"
fun _ _ = pure ()
```

I'm not sure how difficult this is, but it would be very useful. Here's a hack for doing it via the existing plugin with manual touchups.
1. If the pattern is _, give it a unique name
```Haskell
fun :: String -> Maybe Bool -> IO ()
fun (x:xs) (Just uq123) = putStrLn "example"
fun _ _ = pure ()
```

2. Replace the RHS with an empty `case of`
```Haskell
fun :: String -> Maybe Bool -> IO ()
fun (x:xs) (Just uq123) = case uq123 of -- remember RHS = putStrLn "example"
fun _ _ = pure ()
```

3. Complete the case of
```Haskell
fun :: String -> Maybe Bool -> IO ()
fun (x:xs) (Just uq123) = case uq123 of -- remember RHS = putStrLn "example"
False -> _
True -> _
fun _ _ = pure ()
```

4. Replace the RHS of every branch from 3 with the old one
```Haskell
fun :: String -> Maybe Bool -> IO ()
fun (x:xs) (Just uq123) = case uq123 of
False -> putStrLn "example"
True -> putStrLn "example"
fun _ _ = pure ()
```

5. Inline the case of into the outer pattern match, duplicating the LHS as necessary
```Haskell
fun :: String -> Maybe Bool -> IO ()
fun (x:xs) (Just False) = putStrLn "example"
fun (x:xs) (Just True) = putStrLn "example"
fun _ _ = pure ()
```

In terms of utility, 4 is not *that* useful but I can't think of a better behaviour and I think it's slightly more useful than just replacing it with `_`. User beware if the RHS is a huge do-block (in future this could maybe do something intelligent with let or where, or it could just have a cutoff and put `_` in all but one branches). 5 could be its own refactoring action but I'm not sure how likely a user is to actually need it.

I am not sure if there are any possible issues with GADTs and existentials here as I know pattern match order can matter.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading the existing case split plugin and comparing its behavior with issue #5068. Define how splitting a hovered variable or nested underscore pattern should work across function definitions and cases, including GADTs and existentials; done means the selected pattern is split while the surrounding LHS and RHS are preserved as described.

Written by the indexing model from the issue text.

Assessment

Tech stack
haskell
Domain
devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.