Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new undentation rule for match with #833

Closed
4 tasks done
NinoFloris opened this issue Feb 3, 2020 · 18 comments
Closed
4 tasks done

Add new undentation rule for match with #833

NinoFloris opened this issue Feb 3, 2020 · 18 comments

Comments

@NinoFloris
Copy link

NinoFloris commented Feb 3, 2020

Add new undentation rule for match with

I propose we add a new undentation rule for

match 
    long/multi line of code
with
| Foo -> false
| Bar -> true

The existing way of approaching this problem in F# is quite ugly

match 
    long/multi line of code
    with
    | Foo -> false
    | Bar -> true

or what I catch myself doing a lot without really needing that local at all beyond the match is

let x = 
    long/multi line of code
match x with
| Foo -> false
| Bar -> true

Pros and Cons

The advantages of making this adjustment to F# are: easier to use match statements with large expressions

The disadvantages of making this adjustment to F# are: I'm not sure if this would be breaking anything

Extra information

Estimated cost (XS, S, M, L, XL, XXL): XS

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • [?] This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this
@7sharp9
Copy link
Member

7sharp9 commented Feb 3, 2020

Why not:

match code with
| Foo -> false
| Bar -> true

@NinoFloris
Copy link
Author

@7sharp9 I updated the examples, I meant this to work for long expressions

@0x53A
Copy link
Contributor

0x53A commented Feb 3, 2020

This proposal kind of matches how IF THEN can be structured

if
    true
then
    printfn "Hello World"
match
    x
with
| ... ->
    printfn "Hello World"

@NinoFloris
Copy link
Author

@0x53A exactly, thanks for that addition.

@7sharp9
Copy link
Member

7sharp9 commented Feb 3, 2020

What about interacting with the existing multiple matches cancelling indents?

@7sharp9
Copy link
Member

7sharp9 commented Feb 3, 2020

Devils avocado: you could just bind the long expression first:

let whatsit = unfeasible_large_expression_thingy
match whatsit with
| ...

@cartermp
Copy link
Member

cartermp commented Feb 4, 2020

I personally think this is reasonable. @dsyme?

@bartelink
Copy link

bartelink commented Feb 4, 2020

I prefer @7sharp9's avocado being forced. If you want to avoid naming a variable, the following also happens to work (providing an unindentation too ):-

``long/multi line of code`` |> function
| Foo -> false
| Bar -> true

though I'd tend to indent as follows in any case where I would apply this :

``long/multi line of code`` |> function
    | Foo -> false
    | Bar -> true

The above tricks have attractions. In practice, however, simply doing a let first tends to be far mode legible in the general case (and with less vertical space wasted).

@smoothdeveloper
Copy link
Contributor

I noticed the indentation rule for match with is inconsistent with try with, I believe they should be the same.

@abelbraaksma
Copy link
Member

@bartelink, you probably meant it without the backticks? Like so:

long/multi line of code 
|> function
| Foo -> false
| Bar -> true

This aligns nicely and reads even better if you have multiple chained pipes already in the long line part. It's a common practice in my code, but some might argue that an explicit and indented match would be clearer on the eyes.

@7sharp9
Copy link
Member

7sharp9 commented Feb 4, 2020

That reads a lot worse to me, I dislike the function keyword as it makes you infer the type rather than explicitly state it as in a match. I feel like the indentation keeps you sane in f#, dropping it is just an excuse for not refactoring into understandable blocks.

@abelbraaksma
Copy link
Member

I dislike the function keyword as it makes you infer the type rather than explicitly state it as in a match

@7sharp9 I don't understand this? Neither of those have an explicit type annotation. And x |> function is just short for match x with. There are cases where either one or the other creates less bindings, but other than that, I don't see how it relates to inference.

@7sharp9
Copy link
Member

7sharp9 commented Feb 4, 2020

The function keyword is like information hiding, it always takes longer to understand the intent, I would be happy with this removed.

@abelbraaksma
Copy link
Member

abelbraaksma commented Feb 4, 2020

@7sharp9, well, I guess that's a matter of personal taste, then. I don't see it hiding anything, at least not more than, say, piping, or composition (both possible with function, not with match), or with inference, which allows us to write code without too many type annotations. That's too say, all these coding patterns hide information in some way.

I think they both have merit. If you have this code:

someList
|> List.map someMapper
|> List.tryPick (function Foo _ -> None | Bar x -> Some x)
|> function
| Some _ -> printfn "found" 
| None -> printfn "not found" 

then, writing this be code with match is way more verbose, and, imo, doesn't add to readability. More the opposite.

In other cases, match is, of course, more convenient. It depends, and I think we just ought to use what's best / easiest, / most readable in any given situation (which of course highly depends on a programmer's personal taste).

BTW, this is not to say I'm against the proposal, I support it. Just trying to show the existing options as well.

@matthid
Copy link

matthid commented Feb 4, 2020

If needed, I tend to:

match parent.Nodes()
      |> Seq.tryFind (fun e -> match e with | :? XElement as e -> e.Name.LocalName = node | _ -> false)
      |> next
	  |> other with
| Some existingNode -> existingNode :?> XContainer
| None -> // ..

But yes this feels a bit like an inconsistency, but is it? I tend to agree with @smoothdeveloper and the try-with argument. Definitely something I have run into in the past.

@realparadyne
Copy link

I also don't see 'function' as hiding anything in this case.

If you have:

let f x = function true -> x+1 | false -> x

Then that is somewhat hiding that f takes a second argument especially if you're not used to seeing that style. But in the case of piping into a function it seems obvious what's being piped in.

I do think it would be more obvious if you could pipe into the match rather than into function, like this:

someList
|> List.map someMapper
|> List.tryPick (function Foo _ -> None | Bar x -> Some x)
|> match
    | Some _ -> printfn "found" 
    | None -> printfn "not found" 

I've used this form for some code that needed to transform something in multiple steps and it's pleasingly elegant, I like not having to make up names for things for temporary bindings.

@dsyme
Copy link
Collaborator

dsyme commented Jul 5, 2021

@Happypig375 I've marked this as approved-in-principle (meaning the original suggestion)

@dsyme
Copy link
Collaborator

dsyme commented Aug 24, 2021

Completed in preview dotnet/fsharp#11772

@dsyme dsyme closed this as completed Nov 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants