score:5

Accepted answer

short answer.

in haskell there are no implicit conversions. also there are no union types - only disjoint unions(which are algebraic data types). so you can only write:

somelist :: [intorchar]
somelist = [in 1, ch 'c']

longer and certainly not gentle answer.

note: this is a technique that's very rarely used. if you need it you're probably overcomplicating your api.

there are however existential types.

{-# language existentialquantification, rankntypes #-}
class intorchar a where
   intorchar :: a -> either int char

instance intorchar int where
   intorchar = left

instance intorchar char where
   intorchar = right 

data list = nil
          | forall a. (intorchar a) => cons a list

somelist :: list
somelist = (1 :: int) `cons` ('c' `cons` nil)

here i have created a typeclass intorchar with only function intorchar. this way you can convert anything of type forall a. (intorchar a) => a to either int char.

and also a special kind of list that uses existential type in its second constructor. here type variable a is bound(with forall) at the constructor scope. therefore every time you use cons you can pass anything of type forall a. (intorchar a) => a as a first argument. consequently during a destruction(i.e. pattern matching) the first argument will still be forall a. (intorchar a) => a. the only thing you can do with it is either pass it on or call intorchar on it and convert it to either int char.

withhead :: (forall a. (intorchar a) => a -> b) -> list -> maybe b
withhead f nil = nothing
withhead f (cons x _) = just (f x)

intorchartostring :: (intorchar a) => a -> string
intorchartostring x = 
   case intorchar of
    left i -> show i
    right c -> show c

somelistheadstring :: maybe string
somelistheadstring = withhead intorchartostring somelist

again note that you cannot write

{- wont compile
safehead :: intorchar a => list -> maybe a
safehead nil = nothing
safehead (cons x _) = just x
-}

-- this will
safehead2 :: list -> maybe (either int char)
safehead2 nil = nothing
safehead2 (cons x _) = just (intorchar x)

safehead will not work because you want a type of intorchar a => maybe a with a bound at safehead scope and just x will have a type of intorchar a1 => maybe a1 with a1 bound at cons scope.

score:2

i think that the distinction that is being made here is that your algebraic data type intorchar is a "tagged union" - that is, when you have a value of type intorchar you will know if it is an int or a char.

by comparison consider this anonymous union definition (in c):

typedef union { char c; int i; } intorchar;

if you are given a value of type intorchar you don't know (apriori) which selector is valid. that's why most of the time the union constructor is used in conjunction with a struct to form a tagged-union construction:

typedef struct {
          int tag;
          union { char c; int i; } intorchar_u
} intorchar;

here the tag field encodes which selector of the union is valid.

the other major use of the union constructor is to overlay two structures to get an efficient mapping between sub-structures. for example, this union is one way to efficiently access the individual bytes of a int (assuming 8-bit chars and 32-bit ints):

union { char b[4]; int i }

now, to illustrate the main difference between "tagged unions" and "anonymous unions" consider how you go about defining a function on these types.

to define a function on an intorchar value (the tagged union) i claim you need to supply two functions - one which takes an int (in the case that the value is an int) and one which takes a char (in case the value is a char). since the value is tagged with its type, it knows which of the two functions it should use.

if we let f(a,b) denote the set of functions from type a to type b, we have:

f(intorchar,b) = f(int,b) \times f(char,b)

where \times denotes the cross product.

as for the anonymous union intorchar, since a value doesn't encode anything bout its type the only functions which can be applied are those which are valid for both int and char values, i.e.:

f(intorchar,b) = f(int,b) \cap f(char,b)

where \cap denotes intersection.

in haskell there is only one function (to my knowledge) which can be applied to both integers and chars, namely the identity function. so there's not much you could do with a list like [2, 'b'] in haskell. in other languages this intersection may not be empty, and then constructions like this make more sense.

to summarize, you can have integers and characters in the same list if you create a tagged-union, and in that case you have to tag each of the values which will make you list look like:

[ i 2, c 'b', ... ]

if you don't tag your values then you are creating something akin to an anonymous union, but since there aren't any (useful) functions which can be applied to both integers and chars there's not really anything you can do with that kind of union.

score:3

in scala there are types that include both int and char such as anyval and any, which are both supertypes of char and int. in haskell there is no such hierarchy, and all the basic types are disjoint.

you can create your own union types which describe the concept of 'either an int or a char (or you could use the built-in either type), but there are no implicit conversions in haskell to transparently convert an int into an intorchar.

you could emulate the concept of 'any' using existential types:

data anybox = forall a. (show a, hashable a) => ab a

heterolist :: [anybox]
heterolist = [ab (1::int), ab 'b']

showwithhash :: anybox -> string
showwithhash (ab v) = show v ++ " - " ++ (show . hash) v

let strs = map showwithhash heterolist

be aware that this pattern is discouraged however.


Related Query

More Query from same tag