Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could we abstract over type classes?

Tags:

haskell

I wonder if ithere is a deeper reason that we cannot abstract over type classes (or can we?).

For example, when we have

fzip :: (forall a.[a] -> [a]) -> [b] -> [c] -> [(b,c)]
fzip f xs ys = zip (f xs) (f ys)

then we can say

fzip (drop 42) [1..100] ['a'..'z']
fzip reverse   [1..100] ['a'..'z']

and so on. But we cannot

fzip (map succ) [1..100] ['a'..'z']

which we can fix with:

ezip :: (Enum b, Enum c) => (forall a.Enum a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
ezip f xs ys = zip (f xs) (f ys)

and likewise we can fix

fzip (map (5*)) [1..100] [1.5, 2.3, 4.7]

with

nzip :: (Num b, Num c) => (forall a.Num a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
nzip f xs ys = zip (f xs) (f ys)

But is it not embarassing that we cannot subsume ezip and nzip with something like:

gzip :: (g b, g c) => (forall a. g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]

though the code is absolutly identical, up to the name of the class? Or can we somehow?

Interestingly, when instances were just records that contained functions, this would be easily possible.

like image 498
Ingo Avatar asked Dec 01 '15 20:12

Ingo


People also ask

Can abstract classes be used as a type?

The abstract type is often used as a return type (because you don't want the caller to know what concrete type is returned: it could change later, or could vary based on the arguments of the configuration).

Can abstract classes override?

A subclass must override all abstract methods of an abstract class. However, if the subclass is declared abstract, it's not mandatory to override abstract methods.

Can an abstract class be a type Java?

Abstract class: is a restricted class that cannot be used to create objects (to access it, it must be inherited from another class). Abstract method: can only be used in an abstract class, and it does not have a body. The body is provided by the subclass (inherited from).

What classes should be abstract?

In general, a class should be abstract when you have absolutely no reason to create an instance of that class. For example, suppose you have a Shape class that is the superclass of Triangle, Square, Circle, etc.


2 Answers

You can almost do this with ConstraintKinds:

{-# LANGUAGE ConstraintKinds, RankNTypes #-}

import Data.Proxy

gzip :: (g b, g c) => Proxy g -> (forall a . g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
gzip _ f xs ys = zip (f xs) (f ys)

test1 = gzip (Proxy :: Proxy Enum) (map succ) [1 .. 100] ['a' .. 'z']
test2 = gzip (Proxy :: Proxy Num) (map (5*)) [1 .. 100] [1.5, 2.3, 4.7]

The main difference is that you need the Proxy argument, because GHC is unable to infer the right instantiation for g without help.

like image 134
kosmikus Avatar answered Oct 18 '22 00:10

kosmikus


Add a Proxy argument for the constraint:

{-# LANGUAGE PartialTypeSignatures #-}

import Data.Proxy

gzip :: (g b, g c) => Proxy g -> (forall a. g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
gzip _ f bs cs = zip (f bs) (f cs)

> gzip (Proxy :: _ Enum) [0, 1] "ab"
[(1,'b'),(2,'c')]

GHC considers constraint parameters that only occur in constraints ambiguous, so we need to record them explicitly in a Proxy.

like image 42
András Kovács Avatar answered Oct 18 '22 01:10

András Kovács