2

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.

  • 7
    _"Do I have to define separate functions (...)"_ Yes. These are called [smart constructors](https://wiki.haskell.org/Smart_constructors). – AJF Dec 20 '17 at 22:35

2 Answers2

2

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.

Rein Henrichs
  • 15,437
  • 1
  • 45
  • 55
  • Hi Rein, thank you for your answer. I added an addendum emphasizing that I am indeed interested in a general, idiomatic approach to do this in Haskell. – problemofficer - n.f. Monica Dec 20 '17 at 23:02
  • 1
    Creating an `X` from a `Y` is what functions do. If you have an `X` and you want a `Y`, write a function of type `X -> Y` and apply it to your `X`. – Rein Henrichs Dec 20 '17 at 23:04
  • I haven't worked through the answers thoroughly yet, so maybe you already mentioned it, but what "puts me off" in the mere creation of functions is that they are not "bundled" with the type. I understand that the image of a functions creates a relation between the function and the type. Is that all there is? The only other thing I can come up with is to also use a typeclass as a constraint. Maybe it's just the oo thinking that makes we want to bundle it all in one place. – problemofficer - n.f. Monica Dec 20 '17 at 23:10
  • 1
    If that is what puts you off then the only solution is to stop thinking about it that way. "Bundling the functions with the type" is OOP thinking. – Rein Henrichs Dec 20 '17 at 23:15
  • (I mixed up my Xs and Ys a bit there, oops) – Rein Henrichs Dec 20 '17 at 23:16
  • 3
    This doesn't mean that there's no way of organizing similar functions and types together. That's what modules are for. – Rein Henrichs Dec 20 '17 at 23:17
1

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?

AJF
  • 11,767
  • 2
  • 37
  • 64