It seems that I’m not able to change the table of contents in the first post anymore. If I find no other way, I’ll post the final table of contents after the last part of this series.
Intermezzo #1: Back to the Real World
I love happy ends, so it’s a perfect time to end the story of Jeremy, Rupert, and Pit.
The last thing for me to do is to apply the new interface technique to my introductory example: a list of different things. If you remember, I wanted to create a list with String
s, Int
s, and Bool
s:
myListOfThings : List Thing
myListOfThings =
[ AString "one", AnInt 1, AnInt 12, ABool True ]
and then be able to apply functions like
thingSize : Thing -> Int
thingDouble : Thing -> Thing
to the list elements. The problem with the original implementation was that, in order to add yet another wrapped type, for example Char
, we needed to
- Implement the type-specific functions (charSize, charDouble)
- Add the new subtype to the wrapper type
- Add a new case branch to the wrapper functions (thingSize, thingDouble)
I promised that it’s possible to omit tasks #2 and #3, and this is exactly what Jeremy’s magic interface technique enables us to achieve.
First, we have to define the interface type and the operations:
type Thing
= Thing (ThingOperations Thing)
type alias ThingOperations t =
{ size : Int
, double : t
}
I add some convenience functions for the users of the Thing
type:
thingSize : Thing -> Int
thingSize (Thing ops) =
ops.size
thingDouble : Thing -> Thing
thingDouble (Thing ops) =
ops.double
As the designer of the interface, I have to provide:
- the constructor function:
Thing
- a
map
function for the operations record:
thingOps : (r -> t) -> ThingOperations r -> ThingOperations t
thingOps raise ops =
{ size = ops.size
, double = raise ops.double
}
As an implementer of the interface, I have to implement the operations for my internal representation type:
thingImplString rep =
{ size = stringSize rep
, double = stringDouble rep
}
thingImplInt rep =
{ size = intSize rep
, double = intDouble rep
}
thingImplBool rep =
{ size = boolSize rep
, double = boolDouble rep
}
Using the three parts (constructor, map for operations record, implementation of operations), I can provide functions to create the various kinds of things:
aString : String -> Thing
aString =
IF.createInstanceOf Thing thingOps thingImplString
anInt : Int -> Thing
anInt =
IF.createInstanceOf Thing thingOps thingImplInt
aBool : Bool -> Thing
aBool =
IF.createInstanceOf Thing thingOps thingImplBool
I really like the look-and-feel of the code
(Note that I used the interface
function from the last part of the story, but renamed it to createInstanceOf
. I’m still experimenting with the Interface
API…)
Now I can put on the interface user’s hat and create a list of different things:
myListOfThings : List Thing
myListOfThings =
[ aString "one", anInt 1, anInt 12, aBool True ]
Instead of the constructor functions AString
, AnInt
, and ABool
from the former wrapper type, I now use the instance creation functions aString
, anInt
, and aBool
. Nice and easy!
I create a small test program (here’s an Ellie with the code):
main : Html msg
main =
myListOfThings
|> List.map thingSize
|> Debug.toString
|> Html.text
I start elm reactor
, navigate to my source file, and get:
Initialization Error
RangeError: Out of memory
What ??? Has all this just been a fairy tale ???
No, no, no. I need a happy end! The story has to be continued…