[Haskell-cafe] Ambiguous types
Juan Casanova
juan.casanova at ed.ac.uk
Wed Aug 7 13:22:25 UTC 2019
To add and tie this in with the list choice, I must say I said
something not entirely correct: Not any foldable that you may want to
use is valid. As Jaro showed here, Maybe is a Foldable that would not
do what you want.
Now, I have no proof of the following, but I'd bet money that I'm not
wrong that: List is special as a foldable because it is general. In
other words, the function:
tolist :: Foldable t => t a -> [a]
tolist = foldr (:) []
is such that for any x :: t a where Foldable t, it is true that
foldr f v x = foldr f v (tolist x)
which is to say, any structure a foldable may have is preserved when
transforming into list shape, as a foldable.
This is not an argument for saying "let's use lists all the time
instead of foldables". Of course not. But, *internally*, you can use
lists so that the program can run (because it needs an instance),
while knowing that you will not be losing any structure while using
lists, so that the overall function will work for any foldable.
I think I'll stop my ramblings now, I don't want to overload the list.
Quoting Jaro Reinders <jaro.reinders at gmail.com> on Wed, 7 Aug 2019
14:59:43 +0200:
> Another issue with not providing a type signature on internal types is
> that the code can literally change meaning. In this case we can change
> the meaning of your code by simply adding a type signature:
>
> chkDup ns = fst $ foldr f (False,mempty :: (Semigroup a, Num a, Eq a) =>
> Maybe a) ns
> where
> f _ res@(True,_) = res
> f v res@(_,vs) = if v==0 then (False, vs) else (elem v vs, pure v <> vs)
>
> Now the code doesn't do what you want anymore. How should GHC be able to
> infer the types if an expression can have two different types that have
> different meanings?
>
> On 07-08-2019 14:38, Dušan Kolář wrote:
>> Thanks all for the answers, and no, they are not what I was asking for...
>>
>> Not providing type signatures, yes, no type signatures provided,
>> GHC should derive
>> them on its own. My question was, why it is not able to do that and
>> how force GHC
>> to do that. Without extra type info...
>>
>> The original type signature was simple: chkDup :: (Eq a, Num a) =>
>> [a] -> Bool
>> I would be quite happy to have something similar, just for [] to
>> have some generic
>> type - auto-magically :-)
>>
>> And no, my intention was not to make it a bit faster for larger
>> inputs, just make
>> clear and clean code without extra type signatures, easy to read
>> and simple to
>> understand. Real-life input has 36 six members at most.
>>
>> And no, I don't want the general code to be specific for lists,
>> thus no extra type
>> signatures or changes to make it more list of Ints.
>>
>> So thank you once again, unfortunately, my conclusion is that
>> writing generic code
>> is not always beneficial - neither for reading nor for generality...
>>
>> Thank you once again,
>>
>> Dušan
>>
>>
>> On středa 7. srpna 2019 13:21:27 CEST Juan Casanova wrote:
>>> PS: I didn't even spend much time thinking how I would probably
>>> implement chkDup differently even for the generic case that you want
>>> to do. As in, intuition tells me you don't really need an applicative
>>> monoid internally to do what you're trying to do. But, as I said,
>>> haven't thought it thoroughly, and with the code I gave you, you get
>>> what you were trying to do and it's not "wrong". Maybe just overkill.
>>>
>>> Quoting Juan Casanova <juan.casanova at ed.ac.uk> on Wed, 07 Aug 2019
>>>
>>> 12:17:27 +0100:
>>>> To build up on Paolino's answer, and maybe, if you're like me, give
>>>> you a better intuition as to why your code was incorrect, but
>>>> leading to essentially the same answer he gave.
>>>>
>>>> First, you didn't provide us with the type signatures for chkDup and
>>>> f, but I worked with the following ones to start with:
>>>>
>>>> chkDup :: forall a (t :: * -> *) (f :: * -> *). (Eq a, Num a,
>>>> Foldable t, Foldable f, Applicative f, Monoid (f a)) => t a -> Bool
>>>>
>>>> f :: (Num a, Foldable t, Semigroup (t a), Applicative t, Eq a) => a
>>>> -> (Bool, t a) -> (Bool, t a)
>>>>
>>>> This gives me the same error you get even if I define chkDup _ =
>>>> undefined.
>>>>
>>>> The problem is that you are providing class constraints for the type
>>>> f, which does not appear at all in the signature of the function (t
>>>> a -> Bool), and this makes the typechecker get super confused. Now,
>>>> I know why you thought this was necessary: precisely because of what
>>>> Paolino pointed out: mempty has an undefined type, and you tried to
>>>> fix this by saying "This mempty is of type f, which I don't care for
>>>> the type, just that it is a Semigroup and Applicative". But this is
>>>> a mistake. So, if you just remove the f altogether from the class
>>>> constraints of the chkDup type signature, and leave it like this:
>>>>
>>>> chkDup :: forall a (t :: * -> *) (f :: * -> *). (Eq a, Num a,
>>>> Foldable t) => t a -> Bool
>>>>
>>>> You still get an error, but now you get the actual error message
>>>> that you're looking for: "Could not deduce Foldable t0 arising from
>>>> a use of f".
>>>>
>>>> The right way to fix this is how Paolino said: Specify what Foldable
>>>> you're going to use (recommended: list) (so, use [] instead of
>>>> mempty). The point is, since the type f is not part of the input NOR
>>>> the output of the function chkDup, and instead it is just an
>>>> internal tool that you use to compute chkDup, there's no reason to
>>>> want to be generic about it, there's no advantage in that. The
>>>> advantage in genericity using typeclasses is: 1. If it's in the
>>>> input, you allow the user of the function to provide you with any
>>>> type that fulfills the class. 2. You make it very clear that
>>>> everything you do with that parameter is related to its typeclass
>>>> instance, as there is no way to inspect the actual type. 3. If it's
>>>> in the output, you don't let users of the function make any
>>>> assumption of what that type might look like, other than its
>>>> typeclass instance.
>>>>
>>>> But in an internal tool that doesn't come from outside and isn't
>>>> output by your function, there's no reason to be generic, just use
>>>> lists. The only reason I could see being argued for wanting to be
>>>> generic would be if you'd like the user to provide you with hints as
>>>>
>>>> _______________________________________________
>>>> 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.
> _______________________________________________
> 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.
More information about the Haskell-Cafe
mailing list