Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell get type of algebraic parameter

I have a type

class IntegerAsType a where
  value :: a -> Integer

data T5
instance IntegerAsType T5 where value _ = 5

newtype (IntegerAsType q) => Zq q = Zq Integer deriving (Eq)

newtype (Num a, IntegerAsType n) => PolyRing a n = PolyRing [a]

I'm trying to make a nice "show" for the PolyRing type. In particular, I want the "show" to print out the type 'a'. Is there a function that returns the type of an algebraic parameter (a 'show' for types)?

The other way I'm trying to do it is using pattern matching, but I'm running into problems with built-in types and the algebraic type.

I want a different result for each of Integer, Int and Zq q. (toy example:)

test :: (Num a, IntegerAsType q) => a -> a
(Int x) = x+1
(Integer x) = x+2
(Zq x) = x+3

There are at least two different problems here.

1) Int and Integer are not data constructors for the 'Int' and 'Integer' types. Are there data constructors for these types/how do I pattern match with them?

2) Although not shown in my code, Zq IS an instance of Num. The problem I'm getting is:

Ambiguous constraint `IntegerAsType q'
At least one of the forall'd type variables mentioned by the constraint 
must be reachable from the type after the '=>'
In the type signature for `test':
test :: (Num a, IntegerAsType q) => a -> a

I kind of see why it is complaining, but I don't know how to get around that.

Thanks

EDIT: A better example of what I'm trying to do with the test function:

test :: (Num a) => a -> a
test (Integer x) = x+2
test (Int x) = x+1
test (Zq x) = x

Even if we ignore the fact that I can't construct Integers and Ints this way (still want to know how!) this 'test' doesn't compile because:

Could not deduce (a ~ Zq t0) from the context (Num a)

My next try at this function was with the type signature:

test :: (Num a, IntegerAsType q) => a -> a

which leads to the new error

Ambiguous constraint `IntegerAsType q'
At least one of the forall'd type variables mentioned by the constraint 
must be reachable from the type after the '=>'

I hope that makes my question a little clearer....

like image 518
crockeea Avatar asked Feb 24 '23 02:02

crockeea


2 Answers

I'm not sure what you're driving at with that test function, but you can do something like this if you like:

{-# LANGUAGE ScopedTypeVariables #-}
class NamedType a where
    name :: a -> String
instance NamedType Int where
    name _ = "Int"
instance NamedType Integer where
    name _ = "Integer"
instance NamedType q => NamedType (Zq q) where
    name _ = "Zq (" ++ name (undefined :: q) ++ ")"

I would not be doing my Stack Overflow duty if I did not follow up this answer with a warning: what you are asking for is very, very strange. You are probably doing something in a very unidiomatic way, and will be fighting the language the whole way. I strongly recommend that your next question be a much broader design question, so that we can help guide you to a more idiomatic solution.

Edit

There is another half to your question, namely, how to write a test function that "pattern matches" on the input to check whether it's an Int, an Integer, a Zq type, etc. You provide this suggestive code snippet:

test :: (Num a) => a -> a
test (Integer x) = x+2
test (Int x) = x+1
test (Zq x) = x

There are a couple of things to clear up here.

Haskell has three levels of objects: the value level, the type level, and the kind level. Some examples of things at the value level include "Hello, world!", 42, the function \a -> a, or fix (\xs -> 0:1:zipWith (+) xs (tail xs)). Some examples of things at the type level include Bool, Int, Maybe, Maybe Int, and Monad m => m (). Some examples of things at the kind level include * and (* -> *) -> *.

The levels are in order; value level objects are classified by type level objects, and type level objects are classified by kind level objects. We write the classification relationship using ::, so for example, 32 :: Int or "Hello, world!" :: [Char]. (The kind level isn't too interesting for this discussion, but * classifies types, and arrow kinds classify type constructors. For example, Int :: * and [Int] :: *, but [] :: * -> *.)

Now, one of the most basic properties of Haskell is that each level is completely isolated. You will never see a string like "Hello, world!" in a type; similarly, value-level objects don't pass around or operate on types. Moreover, there are separate namespaces for values and types. Take the example of Maybe:

data Maybe a = Nothing | Just a

This declaration creates a new name Maybe :: * -> * at the type level, and two new names Nothing :: Maybe a and Just :: a -> Maybe a at the value level. One common pattern is to use the same name for a type constructor and for its value constructor, if there's only one; for example, you might see

newtype Wrapped a = Wrapped a

which declares a new name Wrapped :: * -> * at the type level, and simultaneously declares a distinct name Wrapped :: a -> Wrapped a at the value level. Some particularly common (and confusing examples) include (), which is both a value-level object (of type ()) and a type-level object (of kind *), and [], which is both a value-level object (of type [a]) and a type-level object (of kind * -> *). Note that the fact that the value-level and type-level objects happen to be spelled the same in your source is just a coincidence! If you wanted to confuse your readers, you could perfectly well write

newtype Huey  a = Louie a
newtype Louie a = Dewey a
newtype Dewey a = Huey  a

where none of these three declarations are related to each other at all!

Now, we can finally tackle what goes wrong with test above: Integer and Int are not value constructors, so they can't be used in patterns. Remember -- the value level and type level are isolated, so you can't put type names in value definitions! By now, you might wish you had written test' instead:

test' :: Num a => a -> a
test' (x :: Integer) = x + 2
test' (x :: Int) = x + 1
test' (Zq x :: Zq a) = x

...but alas, it doesn't quite work like that. Value-level things aren't allowed to depend on type-level things. What you can do is to write separate functions at each of the Int, Integer, and Zq a types:

testInteger :: Integer -> Integer
testInteger x = x + 2

testInt :: Int -> Int
testInt x = x + 1

testZq :: Num a => Zq a -> Zq a
testZq (Zq x) = Zq x

Then we can call the appropriate one of these functions when we want to do a test. Since we're in a statically-typed language, exactly one of these functions is going to be applicable to any particular variable.

Now, it's a bit onerous to remember to call the right function, so Haskell offers a slight convenience: you can let the compiler choose one of these functions for you at compile time. This mechanism is the big idea behind classes. It looks like this:

class Testable a where test :: a -> a
instance Testable Integer where test = testInteger
instance Testable Int where test = testInt
instance Num a => Testable (Zq a) where test = testZq

Now, it looks like there's a single function called test which can handle any of Int, Integer, or numeric Zq's -- but in fact there are three functions, and the compiler is transparently choosing one for you. And that's an important insight. The type of test:

test :: Testable a => a -> a

...looks at first blush like it is a function that takes a value that could be any Testable type. But in fact, it's a function that can be specialized to any Testable type -- and then only takes values of that type! This difference explains yet another reason the original test function didn't work. You can't have multiple patterns with variables at different types, because the function only ever works on a single type at a time.

The ideas behind the classes NamedType and Testable above can be generalized a bit; if you do, you get the Typeable class suggested by hammar above.

I think now I've rambled more than enough, and likely confused more things than I've clarified, but leave me a comment saying which parts were unclear, and I'll do my best.

like image 147
Daniel Wagner Avatar answered Mar 05 '23 07:03

Daniel Wagner


Is there a function that returns the type of an algebraic parameter (a 'show' for types)?

I think Data.Typeable may be what you're looking for.

Prelude> :m + Data.Typeable
Prelude Data.Typeable> typeOf (1 :: Int)
Int
Prelude Data.Typeable> typeOf (1 :: Integer)
Integer

Note that this will not work on any type, just those which have a Typeable instance. Using the extension DeriveDataTypeable, you can have the compiler automatically derive these for your own types:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Typeable

data Foo = Bar
  deriving Typeable
*Main> typeOf Bar
Main.Foo

I didn't quite get what you're trying to do in the second half of your question, but hopefully this should be of some help.

like image 45
hammar Avatar answered Mar 05 '23 05:03

hammar