[Haskell-cafe] DatatypeContexts / alternative

CASANOVA Juan Juan.Casanova at ed.ac.uk
Tue Feb 23 20:09:21 UTC 2021


Thanks Tom,

After the last email I sent, I was left thinking that the way in which this could be a problem was some generic Functor function that had intermediate values that could violate the constraints, but such that it's type signature respected it, but I didn't think much else on it. I think your example is exactly this: you use the getLine fmap inside the function, which is fine for a general Functor, but produces a temporary no-Ord functor, while the overall function preserves the signature.

And yeah, the only possible way to go around this would be to actually have additional "notes" on the signature of functions which said what it internally needed, which not only is horrendous but I believe crashes strongly against a lot of other things we do in Haskell (or any similar language for that matter)

So, yeah, I understand the issue.

To respond to your last comment. First, I realize this is not what you are saying, but please someone do let me know if at some point my posts feel excessive or out of place. I have a strong issue with both often not being able to tell the social context very well and also being very insecure about making a fool out of myself for that reason. Second, my situation is practical (my research is not on programming languages), but I cannot deny I like an elegant solution like most other people here probably do, so I often go a bit further than strictly necessary out of curiosity. This is not the case in this instance, though. This is still giving me problems and I still haven't found a satisfactory workaround for my particular problem, no matter how "dirty" it would be. I think I'll figure it out eventually, though. It seems to be just a matter of instantiating enough type variables to exactly what I want to use them for.

I do see your suggestion, one that I had considered myself; but it's more useful coming from someone who seems to know what they're doing better than me, so that if I end up deciding to avoid type classes for certain complex things I am more secure it's not just me being dumb, but rather that there be dragons that I don't want to fight. However, a practical issue with avoiding type classes tends to be that function signatures tend to become larger, larger and larger, making the code very very cluttered. So I think that's where the art comes, in finding the balance between those two things. A lot of my complex issues come from a sort-of obsession with making code as modular as possible. This is a long topic and I know it's not a "solvable" problem, but I try to push things as far as I can in that regard (within reason, I try).

Thanks again, this does respond everything I doubted about this particular issue.
Juan.
________________________________
From: Haskell-Cafe <haskell-cafe-bounces at haskell.org> on behalf of Tom Ellis <tom-lists-haskell-cafe-2017 at jaguarpaw.co.uk>
Sent: 23 February 2021 19:27
To: haskell-cafe at haskell.org <haskell-cafe at haskell.org>
Subject: Re: [Haskell-cafe] DatatypeContexts / alternative

This email was sent to you by someone outside the University.
You should only click on links or attachments if you are certain that the email is genuine and the content is safe.

On Tue, Feb 23, 2021 at 06:37:06PM +0000, CASANOVA Juan wrote:
> Yes I realize that, but what I am expecting, I guess, is for the
> type checker to tell me (or anyone who tried to use it) that Foo (IO
> String) makes no sense, because Foo is always applied to something
> with Ord. That the very concept of the type Foo (IO String) itself
> does not type check.
>
> I realize the answer might be that this is impossible with current
> Haskell, but then a) Is there any fundamental reason why it is
> impossible

Yes, there's a fundamental reason.  Neither you nor the compiler can
possibly know when invalid Foos (i.e. those whose parameter isn't Ord)
are constructed inside a function polymorphic in a  Functor . Let's
extend my example.

    mapToUnit :: Functor f => f a -> f ()
    mapToUnit = fmap (const ()) . fmap (const getLine)

    whatShouldItDo :: Foo ()
    whatShouldItDo = mapToUnit (Foo (Data.Map.singleton () ()))

Do you agree that  whatShouldItDo  is well typed?  If not, why not?
If so, how should it behave when evaluated?  The implementation of
 mapToUnit , combined with your  Functor  instance says that  fromList
must be run on  [(getLine, getLine)] .  But this is impossible!

Accepting that  whatShouldItDo  is well typed implies that you must
accept that your  Functor  instance for  Foo  cannot work.  (I don't
believe any instance can work if it is law-abiding but I don't know of
a proof.)

There is a cottage industry of trying to work around this problem.
See, for example [1].  I have never seen a satisfactory solution.

Based on your posts here you are frequently running into the limits of
type class based programming in Haskell.  If your purpose is practical
I strongly advise you to stay away from type classes for anything
non-trivial. Stick to the value level. Your life will be much
easier. If your purpose is research into type classes, or general
interest, then good luck and enjoy!

Tom


[1] https://jeltsch.wordpress.com/2015/09/03/constrained-monads/


> ________________________________
> From: Haskell-Cafe <haskell-cafe-bounces at haskell.org> on behalf of Tom Ellis <tom-lists-haskell-cafe-2017 at jaguarpaw.co.uk>
> Sent: 23 February 2021 18:29
> To: haskell-cafe at haskell.org <haskell-cafe at haskell.org>
> Subject: Re: [Haskell-cafe] DatatypeContexts / alternative
>
> This email was sent to you by someone outside the University.
> You should only click on links or attachments if you are certain that the email is genuine and the content is safe.
>
> On Tue, Feb 23, 2021 at 06:14:59PM +0000, CASANOVA Juan wrote:
> > module DatatypeContextsExample where
> >
> > import Data.Map
> > import Data.Bifunctor
> >
> > data Foo t = Foo (Map t t)
> >
> > instance Functor Foo where
> >     fmap f (Foo m) = Foo (fromList (fmap (bimap f f) (toList m)))
> >
> > This does not compile, because I am using toList and fromList, which
> > require (Ord t). But I cannot really have Foo be a functor in this
> > way without it. The thing is, every time I am going to use it, t is
> > actually going to be Ord. But how do I tell Haskell to have this
> > constraint?
>
> You say that every time you are going to use it t is actually going to
> be Ord, but how does the compiler know that when it comes to type
> check fmap?  After all, somewhere else you could write
>
>     fmap (const getLine) :: Foo t -> Foo (IO String)
>
> and  IO String  is certainly not Ord.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
The University of Edinburgh is a charitable body, registered in Scotland, with registration number SC005336. Is e buidheann carthannais a th' ann an Oilthigh Dh?n ?ideann, cl?raichte an Alba, ?ireamh cl?raidh SC005336.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20210223/162b997b/attachment-0001.html>


More information about the Haskell-Cafe mailing list