curried function constraints - Haskell

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.

Torsten Schmits

I always format constraints like this:

foo ::
  TypeclassA m =>
  TypeclassB m =>
  TypeclassC m =>
  TypeclassD m =>
  Int ->
  m Text

because it is nicely consistent, especially for deletion and insertion.
But I've never seen any code do it like this. I guess there's a small semantic difference, but I haven't encountered a problem with it.
Why is this not popular?

TheMatten

I guess simply because we have tuples for this purpose - but they're not strictly needed for passing dictionaries to functions, that's true

TheMatten

PureScript does that

Torsten Schmits

does what? use this style? require tuples?

Fintan Halpenny

I used it occasionally. I like it because it makes it easy to add and delete Show instances when debugging

Torsten Schmits

@Fintan Halpenny right, so what keeps you from using it exclusively? what's your downside?

Fintan Halpenny

I don't write Haskell professionally which is my greatest downside

Fintan Halpenny

But sticking with how people are used to seeing it is my only excuse. Otherwise it totally makes sense

Torsten Schmits

ah ok :big_smile: I wonder how many people have a similar experience

Torsten Schmits

I was looking at ormolu and its github issues today and wondered about a few things – especially since they apparently use a principled approach that differs from common formatting a bit

Torsten Schmits

maybe I should get involved :expressionless:

Vladimir Ciobanu

I also always write them like that unless forced to otherwise (for example, when declaring constraints for instances or type classes). My excuse is because the first real application I built was Purescript, rather than Haskell.

Torsten Schmits

awesome! I'm not alone after all

Torsten Schmits

another question, @Fintan Halpenny and @Vladimir Ciobanu :
when you use curried style, do you place the => at the beginning or end of the line?

TheMatten

Not one of them, but in PS I put everything at the beginning, including =>

Vladimir Ciobanu

I place them at the beginning. I usually format as

foo
  :: forall x y
   . Constraint x
  => Constraint y
  => Bar x
  -> y
foo = ...
Georgi Lyubenov // googleson78

I prefer trailing because

  • less indentation
  • when you have non-vanilla function kinds (e.g. linear) the arrow's "kindness" almost always refers to what's on the left, meaning that if you write them leading you have to look on the previous row, instead of immediately behind the arrow. So everything is trailing, for consistency with the arrows
  • you don't have to align ., which is only one symbol, with the other things which are two
Georgi Lyubenov // googleson78
foo ::
  forall x y.
  Constraint x =>
  Constraint y =>
  Bar x ->
  y
foo = ...
Georgi Lyubenov // googleson78

if you use VDQ it's also annoying, because you need to know if the thing on the left of the -> is a forall to tell if it's a VDQ or not

Vladimir Ciobanu

I find it really hard to read code like that. I also don't like how it looks like, which turns out to be a big deterrent to me trying it out for a while. :upside_down:

Torsten Schmits

so copying, deleting and inserting lines into signatures are not high-priority edit actions for you?
I also find it pretty confusing that the visual indicator for the semantics of an identifier is in the following line.

Fintan Halpenny

I would do before, but I'm seeing the benefits of trailing after writing Rust :sweat_smile:

Torsten Schmits

how does it look in Rust?

Fintan Halpenny

Well the Rust formatter tends to add trailing commas is what I meant

Torsten Schmits

that's something I desperately want for haskell as well :sad:

Fintan Halpenny

The trait bounds are usually done by having a where clause that lists the generic parameters with trailing commas

Fintan Halpenny

But you do stuff like T: Clone + Debug for a single parameter

Torsten Schmits

Vladimir Ciobanu said:

I find it really hard to read code like that.

can you describe why that is?
I would also find it nicer if the operator identifying the semantic role of the signature component were at the beginning of the line, but since your presented style creates ambiguities because e.g. constraints can be prefixed by any of ::, . or =>, the cognitive overhead seems obvious to me.

Vladimir Ciobanu

I think it's mostly habit. I usually use explicit forall even when they're not needed (a habit I got from Purescript), so I only have constraints after . or =>. I've gotten used to scanning type declarations and noticing where the => turns to ->. The problem with using them at the end of the line is alignment: my eyes need to zig-zag to parse. I find that to be a bigger hassle than having developed the heuristic of finding the "last =>".

Georgi Lyubenov // googleson78

what do you mean by "zig-zag to parse"?

Vladimir Ciobanu
foo
  Constraint x =>
  SomeOtherConstraint y =>
  Input x ->
  SomethingElse ->
  y

The => and -> are not aligned, so it's harder to scan the function constraints vs inputs easily. At least, it is for me. The advantage here is, the end of the line tells you what kind of thing it is, constraint vs input. This is what I meant by 'zig-zagging', eyes go from left to right searching for those arrows, since they are not vertically aligned.

foo
  :: Constraint x
  => SomeOtherConstraint y
  => Input x
  -> SomethingElse
  -> y

This vertically aligns the arrows, which makes them really easy to vertically scan, but then there's some footnotes, such as a constraint may start with :: or =>, and an input may start with => or -> (but only if it's the first input, subsequent inputs must start with ->. Then also, the output starts with ->, but it's the last one.

What I meant is, my brain is now used to these weird rules and I can easily scan type declarations. But if I moved the arrows at the end, I'd have to develop new heuristics. Additionally, I think that (for me at least), it's easier to follow vertically aligned code even with the caveats/special rules described above than to the zig-zag I mentioned.

Fintan Halpenny

If it's trailing the rule is that "the symbol to the right tells you what's on the left" so => on the right says the thing on the left is the constraint, -> tells you it's a parameter, and nothing tells you it's the final return type. Which I think is pretty cool

Fintan Halpenny

And _seems_ to be unambiguous

Fintan Halpenny

It's a mix and match for the prefixing version

Torsten Schmits

@Vladimir Ciobanu wonder if that is a neuological property. for me, it is hard in general to read repetitions. it's virtually impossible for me to count the characters in 000000000000 (maybe it's my astigmatism). so when I look at the aligned variant, I have a bit of a hard time keeping the focus on the right row, especially since the arrows are visually similar.

for the trailing variant, I assume that saliency is the main feature. the arrows have distinct shapes compared to the rest of a row, so I think I'm getting an instant map of the signature without parsing. maybe that's something my brain is overcompensating with, for the bad repetition parsing capabilities.