Cast Haskell values with Witch

by Taylor Fausak on

Have you ever wanted to convert a Haskell value from one type to another but you weren’t sure how? Or maybe you did know but you weren’t sure if the conversion was safe. I’m happy to announce Witch, a library that aims to make those conversions easy and painless. This post will explain my motivation for creating the library and show some examples of how to use it.

Motivation

When I’m writing Haskell I feel like I spend a considerable amount of time converting values between various types. For example I might have an Int16 on hand but I want to call a function that expects an Int. In that case I would reach for fromIntegral which is maybe the most popular numeric conversion function.

However what happens when I want to perform the opposite conversion from an Int into an Int16? The old reliable fromIntegral claims to work for that but it can give surprising results with no warning. For example weird things happen when you overflow the target type:

>>> fromIntegral (32767 :: Int) :: Int16
32767

>>> fromIntegral (32768 :: Int) :: Int16
-32768

This makes fromIntegral tricky to use. In some cases it’s perfectly safe and in fact the preferred way to do a conversion. But in other cases it’s dangerous and should be avoided in favor of a safer conversion function. (In this particular case that function is toIntegralSized.)

This same pattern plays out with different types and different type classes all throughout the Haskell ecosystem. For example realToFrac can be idiomatic or unsafe depending on context. Same with fromString.

But let’s say these conversion functions are always safe. It can still be annoying to keep them all in mind and remember when to use them. For instance the time library encourages using fromIntegral to produce NominalDiffTime values. That always takes me a few seconds to remember.

Examples

So how does Witch help with all this? It provides two type classes: From for conversions that always succeed and TryFrom for those that sometimes fail. It also provides many useful instances and convenient helper functions. Plus writing new instances is a breeze. Let’s take a look!

Basics

The From type class and corresponding from method can be used for conversions that always succeed. A good example of that is what I mentioned earlier, converting from an Int16 into an Int. Here’s how to do that with Witch:

>>> from (1 :: Int16) :: Int
1

Similarly the TryFrom type class and tryFrom method can be used for conversions that sometimes fail. Let’s try that problematic conversion from before:

>>> tryFrom (32767 :: Int) :: Either (TryFromException Int Int16) Int16
Right 32767

>>> tryFrom (32768 :: Int) :: Either (TryFromException Int Int16) Int16
Left (TryFromException @Int @Int16 32768 Nothing)

Huzzah! We accurately captured that the conversion might fail. And when we tried to perform the conversion it caught the problem for us.

But looking at that example you might have some questions: Do we need all those type signatures? What if I want it to explode when something goes wrong? Can I use this with Template Haskell?

Type Applications

You do not need all those type signatures. The Witch library is designed to be used with the TypeApplications language extension. That extension makes it easy to specify the input and output types without having to provide clunky type signatures. Here are the previous examples with type applications:

>>> from @Int16 @Int 1
1

>>> tryFrom @Int @Int16 32767
Right 32767

>>> tryFrom @Int @Int16 32768
Left (TryFromException @Int @Int16 32768 Nothing)

Impure Exceptions

Maybe you want to throw an impure exception when something goes wrong. Perhaps you know something that you can’t prove to the compiler, or maybe you’re writing a quick script and crashing is acceptable. In any case you can use the unsafe functions in Witch to do the conversions:

>>> unsafeFrom @Int @Int16 32767
32767

>>> unsafeFrom @Int @Int16 32768
*** Exception: TryFromException @Int @Int16 32768 Nothing

Template Haskell

You can use Template Haskell to perform conversions at compile time. The lifted functions in Witch make it easy for you:

>>> $$( liftedFrom @Int @Int16 32767 )
32767

>>> $$( liftedFrom @Int @Int16 32768 )
<interactive>:2:3: error:
     Exception when trying to run compile-time code:
        TryFromException @Int @Int16 32768 Nothing
      Code: (liftedFrom @Int @Int16 32768)
     In the Template Haskell splice $$(liftedFrom @Int @Int16 32768)
      In the expression: $$(liftedFrom @Int @Int16 32768)
      In an equation for it: it = $$(liftedFrom @Int @Int16 32768)

Conclusion

I tried to keep this post short, which means that I wasn’t able to show off everything that Witch is able to do. If this sounds interesting to you but you’re wondering about what else it can do, please check out the documentation on Hackage. Or if you’re curious about how it works behind the scenes, read the source on GitHub.

I hope this post helped you understand my motivation for creating Witch. Please consider using it to handle conversions in your Haskell code, and let me know if you run into any problems or limitations!