In Haskell, what is the analogous pattern to setting up "objects" with constructors?

In object oriented programming languages you often have multiple constructors that set up objects of a class, for example a MyInteger object could be constructed from an built-in int or a string:

class MyInteger {
    int _value;

    MyInteger (int value) {
        _value = value;
    }

    MyInteger (String value) {
        _value = String.parseInt(value);
    }
}

What would be the idomatic way to do this in Haskell? As far as I understand in Haskell the sum type

data MyInteger = Integer Int | String String

is either an Integer or String and not a single type like in the oo-example above. And I can't define a logic for the value constructor String that would take a String and "return" a MyInteger.

Do I have to define separate functions like this:

myIntegerFromString :: String -> MyInteger
myintegerFromString inputString = ...<parsing logic on inputString>....

and then call it whenever I want to create a MyInteger from a string

 main = do
     ...
     let aMyInteger = myIntegerFromString "4"
     ...

Addendum: The creation of an "object" from Int and String was merely meant as a minimal example. I am more interested in the general approach of having variables of a type X that can be created from various other types Y,Z,... and how this would be solved in Haskell. Precisely because I don't want to misuse Haskell while thinking in object-oriented ways, I am asking for an idiomatic pattern to model this in Haskell. This is why a solution that solves the parsing of Ints from Strings is too specific.

Upvotes: 2

Views: 156

Answers (2)

AJF
AJF

Reputation: 11913

Haskell programmers use the 'smart constructor' pattern to do this. To be clear, this isn't a design choice, this is a design necessity. It is simply not possible to do exactly what you do in your OOP example in Haskell.

In fact, you can see this style in the Data.Text module. Here's the raw definition of the Text type:

data Text = Text
    {-# UNPACK #-} !A.Array
    {-# UNPACK #-} !Int
    {-# UNPACK #-} !Int
    deriving (Typeable)

I wouldn't want to touch that even if I were an expert. However, there is a nice little smart constructor right there: (though admittedly it doesn't perform checks)

pack :: String -> Text
pack = unstream . S.map safe . S.streamList -- Scary internals handled cleanly.

But why don't we have this kind of object-oriented style built in? Because the internal workings of the constructor is hidden from you, you don't know exactly what might be going on when you write new MyInteger("123"). This is in direct violation of one of Haskell's fundamental principles, Referential Transparency.

So instead, a Haskell programmer would probably translate the above class as:

newtype MyInteger = MyInteger Integer

parseMyInteger :: String -> MyInteger
parseMyInteger = -- (whatever implementation here)

In conclusion, forget object-oriented programming when writing Haskell (except if you're being experimental). Haskell has an entirely different paradigm, so use that instead of OOP concepts.


Some optional reading: What is referential transparency?

Upvotes: 1

Rein Henrichs
Rein Henrichs

Reputation: 15605

In your example, MyInteger always contains an int but is sometimes constructed from a String by parsing the string into an int first. This is not equivalent to data MyInteger = Integer Int | String String but to newtype MyInteger = MyInteger Int with a smart constructor for constructing from a string:

newtype MyInteger = MyInteger Int

mkMyIntegerStr :: String -> MyInteger
mkMyIntegerStr = MyInteger . read

with the caveat that not all strings have a valid parse as an int and that acknowledging this failure mode in the type system would be more robust:

mkMyIntegerStr :: String -> Maybe MyInteger
mkMyIntegerStr = fmap MyInteger . readMaybe

with readMaybe from the Text.Read module.

In general, though, I think it is best to approach Haskell with fresh eyes rather than to try to map familiar concepts from OOP onto it. Trying to treat FP as a weird form of OOP has lead many learners astray.

Upvotes: 2

Related Questions