Reputation: 2297
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 Int
s from String
s is too specific.
Upvotes: 2
Views: 156
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
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