JeanJouX
JeanJouX

Reputation: 2741

Building two distinct modules with the same source code with Haskell

I'm writing a module with Haskell and I would like to create two versions of this module :

For convenience, I would like these two modules (with two differents names) to share the same source files. the two modules will have the same type and same functions with some differences ('extended' version will rely on the 'basic' one).

My idea is to have something like this :

module MyLib where  -- for 'basic' version

module MyLibExt where  -- for 'extended' version

MyType = 
         TypeA            -- for 'basic' version
       | TypeB            -- for 'basic' version
       | TypeC            -- for 'basic' version

       | TypeExtendedD    -- for 'extended' version
       | TypeExtendedE    -- for 'extended' version


MyFunction TypeA         = ...  -- for 'basic' version
MyFunction TypeB         = ...  -- for 'basic' version

MyFunction TypeExtendedD = ...  -- for 'extended' version

and build the two modules with some compilation directive given to GHC/Cabal.

Is it possible to do a such thing with Haskell ?

What are the GHC/Cabal compilation directive which can be useful to make conditionnal building ?

Upvotes: 3

Views: 77

Answers (2)

luqui
luqui

Reputation: 60513

Consider abstracting. It may not be worth it, but sometimes there is beauty and power hiding, and it can only be teased out with abstraction. For your case MyType and MyFunction, perhaps it goes like this (just an example, and it could look very different depending on your details):

class MyFunction a where
    myFunction :: a -> Int

data MyType = TypeA | TypeB | TypeC

instance MyFunction MyType where
    myFunction TypeA = 0
    myFunction TypeB = 1
    myFunction TypeC = 2

data MyTypeExt = Orig MyType | TypeExtendedD | TypeExtendedE

instance MyFunction MyTypeExt where
    myFunction (Orig x) = myFunction x
    myFunction TypeExtendedD = myFunction TypeC + 1
    myFunction TypeExtendedE = myFunction TypeC + 2

That would be the first level. You can go further, making the extension parametric:

data MyTypeExt a = Orig a | TypeExtendedD | TypeExtendedE

instance (MyFunction a) => MyFunction (MyTypeExt a) where
    ...  -- defined so that it works for any myFunction-able Orig

Then it's easy to separate out into different files. But the advantages of abstracting go far beyond splitting things into public and private spaces. If there are two "valid" versions of your code, then those two versions each have consistent meanings. Find what they have in common, see if you can code what they have in common without referencing either one in particular. Then look carefully -- what else has these things in common with your two examples? Can you build your two examples out of yet simpler pieces that also have those things in common? In Haskell, code programs you.

Upvotes: 0

Paul Visschers
Paul Visschers

Reputation: 584

You can't put two modules in the same file. But you can sort of get what you want with a bit of re-exporting.

One file would have both the basic and extended code (I shortened it a bit):

module MyLibExt where

MyType = TypeA | TypeB | TypeC | TypeExtendedD | TypeExtendedE

myFunction TypeA = ...
myFunction TypeB = ...
myFunction TypeExtendedD = ...

then the other file would be the basic one:

module MyLib (MyType (TypeA, TypeB, TypeC), myFunction)

import MyLibExt

This way if someone imports just MyLib they only get access to the basic constructors, but not the extended ones. myFunction will still work on TypeExtendedD values like it would in MyLibExt, but since we're unable to create those values with just MyLib, that's fine.

More generally, when you define your module, you can say what exactly you want to export. Here are some basic examples:

module Example (
  exampleFunction, -- simply export this function.

  ExampleType1 (), -- export the type, but no constructors.
  ExampleType2 (ExampleConstructor1, ExampleConstructor2), -- export the given type and its given constructors. You can't export constructors like functions, you have to do it like this).
  ExampleType3 (..), -- export given type and all its constructors.

  ExampleClass1, -- export type class, but no functions that belong to it.
  ExampleClass2 (exampleFunction2, exampleFunction3), -- export type class and the given functions belonging to it. You can also export these functions as though they were normal functions, like in the first of the examples.
  ExampleClass3 (..), -- export type class and all its functions.

  module Module1, -- re-export anything that is imported from Module1.
  ) where

You can export anything that is in scope, including anything that you imported from other modules. In fact if you want to re-export something from other modules, you need to explicitly define an export list like this, by default it will only export whatever is defined in this module.

Upvotes: 1

Related Questions