Force type applications on `Foldable`s - Haskell

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

Georgi Lyubenov // googleson78

does anyone know of a way to force users of a function f :: Foldable t => ... to always use TypeApplications? to specify t

TheMatten

@Georgi Lyubenov // googleson78 https://kcsongor.github.io/ambiguous-tags/#an-ambiguous-smart-constructor

Improving code readability by enforcing type annotations.
Georgi Lyubenov // googleson78

ah thanks, I knew I had seen that in his blog, but I didn't remember where/didn't find it

Georgi Lyubenov // googleson78
ln :: forall a -> a -> ()
ln = undefined
--
vdq.hs:1:7: error:
    Illegal symbol forall in type
    Perhaps you intended to use RankNTypes or a similar language
    extension to enable explicit-forall syntax: forall <tvs>. <type>
  |
1 | ln :: forall a -> a -> ()
  |       ^^^^^^
Failed, no modules loaded.
>
TheMatten

It only works with kinds for now

TheMatten

Namespacing problems coming from types written as expressions are not solved yet

TheMatten

or at least not decided upon

Georgi Lyubenov // googleson78

cool, the hack works in my case, thanks!

Kim-Ee Yeoh

does anyone know of a way to force users of a function f :: Foldable t => ... to always use TypeApplications? to specify t

Why would you want to do that? It’s up to the client of your function to decide whether to leave instance resolution to the compiler.

Georgi Lyubenov // googleson78

for readability and maintainability

Georgi Lyubenov // googleson78

foldl1 expensiveOperation is very different depending on if you have a [] or Maybe

Georgi Lyubenov // googleson78

there's also the camp of "I refactored a [a] into a (b, [a]) and it compiled, and I now get length xs == 1 always"

Georgi Lyubenov // googleson78

(instead of length . snd which is what they want)

Georgi Lyubenov // googleson78

it's a slightly more "thrifty" solution (not sure if right word) compared to manual specialisations for all the ts you want to use it for

Kim-Ee Yeoh

Georgi Lyubenov // googleson78 said:

foldl1 expensiveOperation is very different depending on if you have a [] or Maybe

So highlight that point in the documentation of your library.

Kim-Ee Yeoh

Georgi Lyubenov // googleson78 said:

there's also the camp of "I refactored a [a] into a (b, [a]) and it compiled, and I now get length xs == 1 always"

Isn't this an issue orthogonal to the above? Whereas earlier it was about unexpected time complexity of Foldable, now this is a case of unexpected instances of Foldablethat the compiler should have but didn't report as type errors?

The latter isn't your fault.

But if you think the client of your library needs reminding of perilous Foldable instances, they will appreciate the effort you took to add that reminder in your docs.

Georgi Lyubenov // googleson78

even if I highlight it my coworker(or me one week later!) can easily ignore/forget the docs - anything that isn't enforced but merely a suggestion will eventually be "gotten wrong"

Georgi Lyubenov // googleson78

it's not my fault that the instances are there, but by providing explicit applications I can do something to avoid unexpectedly using them - much like how it's usually better when possible to do explicit matches on all constructors vs a _ match, so that when you add a new constructor to your datatype you get reminded by the compiler to write proper logic for it wherever you're using the datatype

Georgi Lyubenov // googleson78

it takes "cognitive load" off me, and puts it on the compiler instead

Georgi Lyubenov // googleson78

tbh if this was a public library I probably wouldn't do this! (or I would do it in some other way, e.g. not use Foldable)

but it's something that we've agreed upon for some specific functions at my workplace :sweat_smile: sorry if this was "missing critical information"

Kim-Ee Yeoh

Georgi Lyubenov // googleson78 said:

even if I highlight it my coworker(or me one week later!) can easily ignore/forget the docs - anything that isn't enforced but merely a suggestion will eventually be "gotten wrong"

It sounds like you're dead set on this novel idea of "forcing type application," in which case I'll refrain from giving more suggestions on what else you could do.

I will say, though, your work environment sounds quite dysfunctional if communicating critical information, whether via docs or otherwise, cuts so against the grain.

Georgi Lyubenov // googleson78

I don't see how communicating information helps the problem of "oops this happened by accident"

Georgi Lyubenov // googleson78

to me this is like saying we don't need a typechecker because you can simply write next to a function what kinds of arguments it takes and returns

Georgi Lyubenov // googleson78

this is not about people not knowing stuff - it's about people forgetting stuff, and even more than that about minimising how much one has to actively think about while writing a program

Kim-Ee Yeoh

Georgi Lyubenov // googleson78 said:

it's not my fault that the instances are there, but by providing explicit applications I can do something to avoid unexpectedly using them - much like how it's usually better when possible to do explicit matches on all constructors vs a _ match, so that when you add a new constructor to your datatype you get reminded by the compiler to write proper logic for it wherever you're using the datatype

This is a false analogy. Yes, the compiler warns of missing pattern matches when new constructors get added.

Ever notice what happens when a haskell beginner enters not at the repl? They get the Show instance not found error for the type of a function.

An instance that should be there isn't, resulting in compiler dyspepsia just like when pattern matching that should be there isn't. That is the true analogy.

Also you aren't "providing explicit applications." Here you are mandating explicit applications by revoking type class instance inference which essentially means getting rid of all of type classes entirely. Far from giving something, you actually want to take things away from the status quo.

Are you alluding to a problem with OverlappingInstances? Or are you trying to mitigate the Foldable instance problem for tuples? The greater clarity you bring to your problem the easier it is to find a proper solution instead of one that brings a raft of new problems.

It sounds like the latter: there is a Foldable instance for tuples that you'd somehow want to suppress. And so the solution you're reaching for is to force type applications.

There are other solutions.

This proposal about "forcing type applications" will absolutely require hacking on the ghc compiler to implement.

Georgi Lyubenov // googleson78
This is a false analogy. Yes, the compiler warns of missing pattern matches when new constructors get added.

I don't see how it's any different from the pov of "this is something the compiler can handle instead of me".

Also you aren't "providing explicit applications." Here you are **mandating** explicit applications by **revoking** type class instance inference which essentially means getting rid of all of type classes entirely. Far from giving something, you actually want to take things away from the status quo.

Just because I'm doing so for one function, doesn't mean I am going to do so for every function in existence (I'm not writing (+) @Int 5 3!!). I would be writing Elm or Go instead, if I wanted to do that.

Are you alluding to a problem with OverlappingInstances? Or are you trying to mitigate the `Foldable` instance problem for tuples? The greater clarity you bring to your problem the easier it is to find a proper solution instead of one that brings a raft of new problems.

It sounds like the latter: there is a Foldable instance for tuples that you'd somehow want to suppress. And so the solution you're reaching for is to force type applications.

There are other solutions.

Would you argue Maybe also shouldn't have Foldable? Because in my original case the two concrete uses are of Foldable [] and Foldable Maybe - and I was not originally trying to prevent anything - it was just clearer to write @Maybe or @[] rather than spend time trying to figure out what type is being used in a code review (this is coming from someone else btw, not me)

This proposal about "forcing type applications" will absolutely require hacking on the ghc compiler to implement.

I am not proposing to change the compiler or anything like that - I was only asking if I can do this thing (and I even got a solution that is good enough for my use case).

Kim-Ee Yeoh

Georgi Lyubenov // googleson78 said:

Just because I'm doing so for one function, doesn't mean I am going to do so for every function in existence (I'm not writing (+) @Int 5 3!!). I would be writing Elm or Go instead, if I wanted to do that.

Agreed. But you still need to be clear with your use of plain language. You are mandating type application for a small set of functions you’ve written. You aren't providing anything. You are demanding that the client of those functions write those type applications. It is the client who is doing the providing because you have insisted on it.

Kim-Ee Yeoh

Georgi Lyubenov // googleson78 said:

Would you argue Maybe also shouldn't have Foldable? Because in my original case the two concrete uses are of Foldable [] and Foldable Maybe - and I was not originally trying to prevent anything - it was just clearer to write @Maybe or @[] rather than spend time trying to figure out what type is being used in a code review (this is coming from someone else btw, not me)

This is what I mean about problem clarity.

Is this an issue of unclear instance resolution leading to poor code readability and maintainability? Because that's a problem of ad-hoc polymorphic code in general.

Poor code maintainability isn't the same problem as erroneous instance resolution that risks a runtime error. The latter cuts deeper because it jeopardizes the assurance that well-typed programs don't go wrong.

Would you argue Maybe also shouldn't have Foldable?

Why "also"? I haven't argued anything that can be construed as a lead-in to this question. If you're referring to the Foldable instance for tuples, that was seeking clarification from you because that was the example you gave earlier. You seem to allude that it is a problem so I wrote what I did hypothesizing on that assumption.

If you really want to know my opinion about Foldable instances, we can discuss in a different thread. I'm quite cognizant of the arguments pro and contra the FTP proposal.

I participated in this conversation in the spirit of nudging it back to X--the problem--rather than letting it fester on a fixation on Y--the attempted solution because as the record demonstrates, we have a classic XY problem on our hands: http://xyproblem.info/

Georgi Lyubenov // googleson78

I started talking about this as different reasons I could think of why you would want to be explicit about what instances you're using (this is what I understood from Why would you want to do that?)

Kim-Ee Yeoh

Georgi Lyubenov // googleson78 said:

I started talking about this as different reasons I could think of why you would want to do this (this is what I understood from Why would you want to do that?)

The "you" in the question was specific. It is a "you with whatever problem you are facing hic et nunc" rather than a hypothetical "you" in all possible worlds.