Cast Haskell values with Witch
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.
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
hand but I want to call a function that expects an
Int. In that case I would
fromIntegral which is maybe the most popular numeric conversion
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
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
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
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
time library encourages using
fromIntegral to produce
values. That always takes me a few seconds to remember.
So how does Witch help with all this? It provides two type classes:
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!
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
>>> from (1 :: Int16) :: Int 1
TryFrom type class and
tryFrom method can be used for
conversions that sometimes fail. Let’s try that problematic conversion from
>>> 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?
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)
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
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)
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!