Join egghead, unlock knowledge.

Want more egghead?

This lesson is for members. Join us? Get access to all 3,000+ tutorials + a community with expert developers around the world.

Unlock This Lesson

Already subscribed? Sign In


    Combine Elm Union Types with Records for Clarity and Robustness

    Enrico BuonannoEnrico Buonanno

    Union types are powerful, but they lack the clarity of records' member names. If a case constructor takes several values, it can be hard to identify what each value represents, and this can lead to bugs. You can combine union types with records and have the best of both worlds.



    Become a Member to view code

    You must be a Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson


    Instructor: 00:02 Imagine in our domain we have this type, Shape, and we need to cater for a few different shapes like sphere, box, and cone. For each case, we have a payload which are the dimensions. The sphere has a radius, the box has the three sides, and, for the cone, we'll need the radius and the height.

    00:22 This code compiles and works. As you can see, you can create a shape, compute its volume, and display it. Now, consider that somebody else, in a different file might want to create a box. It might say something like this. This is a varied box, as far as the compiler goes. We could pass this to here and calculate its volume.

    00:59 Here, you see that there's the problem of inconsistent naming. Here, we're using width, height, and thickness, whereas in our volume function we are using length, height, and depth. It's not really clear what each of these floats represents.

    01:16 Things can get even worse. You could create a cone like this. This too, is perfectly fine code as far as the compiler is concerned, but the result is wrong. Here, we're assuming that the first float is the height, whereas in the code that computes the volume, we're taking the first float to be the radius.

    01:50 You can see how there's a problem, especially when the payload of these union types includes several values of the same type. With records we don't have this problem, because each member is identified with a name. What if we use this strategy?

    02:05 Let's try to fix the example with cone first. Let's say that, rather than having two floats, a cone has one record with the members Radius and Height. To construct a cone, you need to pass it such a record with the height equal to two and a radius of one. You see that in this case the order doesn't matter.

    02:35 When we pattern match, we don't have two values. We have a single value. We could say, cone, and then here say, cone.radius and cone.height. This compiles and you see that our error has been fixed. More interestingly, we could deconstruct this here and say Radius, Height, and just use these names down here.

    03:06 Let me do the same refactoring for box and sphere. Here, where I create a box, I would have to now pass it a record and I would have to provide a value for the length field. Then, it would immediately strike me that I don't have a length variable and that my naming is inconsistent. I could address that.

    03:41 I think this is clear enough. Let me remove the box, and let me finish my refactoring of the volume function. Here, you can see that the compiler picked up on an inconsistency in our naming. Here, I'm saying that a box has a length, and height and depth, whereas it already has length, height and width.

    04:08 Let me fix the definition. Now it compiles. You can see that the code no longer allows for inconsistent naming or for error due to the order in which we pass arguments to the case constructors.