Using a few extensions, I can do something like this:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
type family TF (a :: Bool) where
TF 'True = Int
TF 'False = Double
data D a where
D :: TF a -> D a
Notice the constructor D
can work in two ways. Either:
D :: Int -> D 'True
or
D :: Double -> D 'False
Using this pattern, I can completely change the arguments to the constructor depending on it's type, whilst reusing it's name.
However, I'd also like the number of arguments to depend on it's name.
I know I could just replace some arguments with ()
or Void
but I'd rather remove them entirely.
Is there anyway to do this?
If we don't define a constructor in a class, then the compiler creates a default constructor(with no arguments) for the class. And if we write a constructor with arguments or no arguments then the compiler does not create a default constructor.
To call a function with a variable number of arguments, simply specify any number of arguments in the function call. An example is the printf function from the C run-time library. The function call must include one argument for each type name declared in the parameter list or the list of argument types.
Constructors that can take at least one argument are termed as parameterized constructors.
The technique of having two (or more) constructors in a class is known as constructor overloading. A class can have multiple constructors that differ in the number and/or type of their parameters. It's not, however, possible to have two constructors with the exact same parameters.
I'm not sure if this is exactly what @luqui had in mind in the comment above, but you can create a smart constructor in a type class that dispatches on the type of its first argument (or any fixed number of initial arguments) to determine the count and types of remaining arguments via a type family:
{-# LANGUAGE TypeFamilies #-}
data D = Int2 Int Int
| Double1 Double
deriving (Show)
class D1 a where
d :: a -> T2 a
type family T2 a where
T2 Int = Int -> D
T2 Double = D
instance D1 Int where
d x = \y -> Int2 x y -- or just `d = Int2`
instance D1 Double where
d x = Double1 x -- or just `d = Double1`
After which:
> d (2 :: Int) 3
Int2 2 3
> d (2 :: Double)
Double1 2.0
>
It's not clear to me how to generalize this to a situation that requires conditional dispatch:
data D = IntStringDouble Int String Double
| Int2 Int Int
| Double1 Double
deriving (Show)
since you'd need something like:
T2 Int = a -> T3 a
with T3
an additional type family for the next step in the dispatch, and I don't think there's any way to have the RHS of a type family be polymorphic. Maybe someone else can see a way.
Anyway, it's all probably more trouble than it's worth.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With