Sara Lichtenstein and Taylor Fausak discuss converting between numeric types with polymorphic functions.
Episode 20 was published on 2019-09-13.
>> Hey! Welcome back to the Haskell Weekly podcast. I'm your host Taylor Fausak. I'm the lead engineer here at ITProTV and with me today is Sara Lichtenstein, one of the engineers on my team. Welcome Sara!
>> Thank you!
>> We have been off for the past three weeks while our studios have been renovated but it's good to be back. Thank you for your patience and thanks for listening. Today we're going to be talking about an article by Type Classes called "Rounding". It was published way back on August 14th and we put it in issue 172 of Haskell Weekly. But it was an interesting article and Sara could you tell us a little about why you chose to talk about it today?
>> Sure! So I'm sure everyone has experienced this who's worked in Haskell before where you go on to Stackage or Hoogle and are trying to look up a function to use and there's like four or five different functions with the same type signature and you're like, "What do I do now?" because they all do completely different things and this article covers four of those functions.
>> Exactly. And although all four of those functions come from the wonderfully named RealFrac type class which as you might guess from the name is for types that are both real and fractional. But what does real mean? It's not like not imaginary I guess. Is there a better way to put it than that?
>> I think that is the best way to put it. Real like from math class. Real numbers.
>> Yeah! I think we all learned about this in math, maybe high school? I don't remember. It's been a long time. And then fractional, it means that it supports division although it's not necessarily rational numbers so a lot of the RealFrac type classes can represent things like pi which is not rational.
>> But it is real.
>> It is real, and it is frac. So yeah that's the type class that we're kind of talking about in general today but the four functions in particular are: ceiling, floor, truncate, and unsurprisingly round, since the whole article is called rounding. But Sara could you walk us through ceiling. What is it?
>> Sure! So ceiling is a function that takes one integer and goes up to the next one. So if you have something like 2.1 and you ceiling on it get 3.
>> Mm-hmm. And what happens if the number is negative?
>> Back to the same next integer. So negative numbers are a little opposite so it always looks weird when you see that. So for that same example if you have a -2.1 it goes to -2, because -2 is the next number up.
>> Yeah and this usually trips me up when I look at it because when I see ceiling I think like it goes to the next biggest number and I think "Oh -2 the next biggest number is obviously -3", but really it's the next one like up.
>> In a positive trend, yeah.
>> Going positive. And then you mentioned at the top that a lot of these functions have the same type signature so floor has the exact same type signature but it does something kind of the opposite, right? What does it do
>> It's basically the exact opposite so it goes down to the next integer so in that same example if we have 2.1 we get back 2 but if we have -2.1 one that's where we get -3.
>> Right, so exact opposite but the type signature is exactly the same so that's a little strange.
>> Mm-hmm. We have to actually do our reading.
>> Yeah! Gah! The worst part, reading documentation. And there's a couple more functions with the same type signature so there's truncate. What does that one do?
>> So truncate just cuts off the fractional part so in our 2.1 example we get 2 and in -2.1 we get -2 so we can visualize this kind of like a chain saw just slicing off that number at the decimal.
>> Yeah I always like to think about numbers and chain saws at the same time they go together so well.
>> Exactly it really makes math interesting.
>> Very visual and yeah truncate is pretty interesting because you could implement it in terms of ceiling and floor where if your number is positive you're gonna do floor but if it's negative you're gonna do ceiling.
>> I don't know that it's actually implemented that way but it really helps me visualize when I'm not thinking about chain saws a different way to to think about this one
>> Mm-hmm basically if you have a handle and ceiling in floor than you know truncate
>> There you go and on to the most interesting one now that this whole article is about which is round I think we can all kind of guess what this one does based on the name and our kind of elementary school math but could you tell us exactly what it is that it does
>> Right so round as context clues would assume goes to the nearest integer so in our 2.1 example for +2.1 we get 2 and for -2.1 we get -2 however round has this fun thing once you get to the point 5s that has the statisticians rounding.
>> If you have something like 2.5 it's gonna go just to the next even number so from 2.5 you won't get 3 you'll get 2.
>> Right but then with like 3.5 what happens you go to 3?
>> You get 4.
>> You go to 4, okay.
>> 3's not even
>> So only the even numbers get rounded to on the point 5s
>> yeah you mentioned this is statisticians rounding I've also heard it called bankers rounding I'm sure there are more terms for it yeah it can be a little surprising because usually for me at least I've thought of the point fives rounding up but now they round to the even numbers alright sure why not
>> Yeah I mean in like elementary school math they'd always tell you if it's above if it's point 5 or above you go up so now it's like okay gotta relearn math
>> Yeah thanks elementary school for lying to us so yeah round is again kind of the most complicated one among these functions and in general the way that I think about it day to day is it takes some kind of fractional real number RealFrac and goes into some integral number like you know an int or an integer or a word or a natural something like that so it's a pretty robust function it will do a lot of different conversions for you I just mentioned integral and I think that kind of dovetails nicely with a function that can go in the other direction from the integral type class which is obviously the type class in Haskell for integers and there's a function for that one called fromIntegral where you give it some integral type like int or natural or whatever and it converts it into some numeric type so not even RealFrac just any number basically it could be a complex number could be an imaginary number but yeah it's a real Swiss Army knife of a conversion function because it goes from so many types to so many other types
>> It's such a perfect descriptor for that function because you really do use it for basically any conversions in math at least here
>> Mm-hmm yeah we have this this Swiss Army knife when we pull out the blade that goes double to int or sorry the other way around int to double we pull out the blade that goes natural to float not that you should probably do that conversion in particular but anyway
>> we don't recommend that
>> We use it all the time
>> But we'll get back to that in just a sec I think I want to loop back on what you said at the top of the show about these kind of all having the same type signature but different implementations so we have to go read the documentation and you had a great term for this what did you call it
>> So this kind of reminded me of homophones from English so homophones for words are words that sound the same but mean different things
>> So this is kind of like
>> The progamming version of the same thing
>> These are our coding homophones
>> We have some other good examples in the prelude as well if you want to talk about some of those
>> Sure there's one that deals or there's two I should say the deal with lists and we're going to talk about a couple examples but there are probably hundreds of these things functions that have the same type signature but have different implementations and the first ones that kind of popped into our head were head and last for lists where head will take in a list and give you an element from it the first element last will take in a list and give you the last element from it so they have the exact same type signature but very different implementations and again you have to just go read the documentation or you might be able to tell from the names what they do
>> Right it's always funny to me when we have to read the documentation for these things since Haskell is so lazy but then we can't be that lazy
>> Yep yep and you know that's where good names come in because then you hopefully don't have to read the documentation but then you might get caught up on something like rounding and think you know how it works and
>> And then you don't
>> And then you don't but there are some other ones some other examples of this right that's especially ones that deal with numbers could you talk about some of those
>> Yeah so there are two other great math examples so max and min that are in the prelude max obviously gets the maximum and min obviously gets the minimum and luckily those two have descriptive enough names where at least for me I'm not going to need to go and look at documentation I can kind of infer what those do
>> Mmm-hmm even though they have the same type signature unless the person that wrote them is trying to trick us we can probably guess what they're doing
>> Which hopefully no one who writes things is trying to trick us
>> yeah a lot of things start to fall apart if that happens so we've been talking about these kind of coding homophones do you feel like it these come up because they're type signatures are the same but their implementations can differ do you feel like it's worth it in general to try to make their types different by introducing like a bunch of new type wrappers or type aliases or something or is it just kind of that cost of doing business we have to deal with these
>> Well I think it's kind of situational for example when we're doing something like fromIntegral I know that we like to make wrappers and functions and things that we can use to make that more suitable for us that way we don't get as many errors and stuff like that but sometimes it's just as easy to use these polymorphic functions
>> Mm-hmm yeah and you mentioned that we sometimes use kind of monomorphic versions of fromIntegral and I'm sure our code base is littered with things like intToDouble equals fromIntegral or naturalToFloat equals from integral
>> I think just yesterday I did doubleToNatural which was fromIntegral
>> yeah and it can seem kind of silly to define all of these functions that are just different names for this existing polymorphic one but the benefit comes like you said from really pinning down the types that are involved so that if you're doing a bigger computation Haskell by default will just sort of pick a type kind of arbitrarily in the middle like oh you didn't tell me what to use here so I'll use double and that's glossing over a lot of details but by using these monomorphic functions we can make it really clear to the reader of that code that this is the type we want to use in between all these other things without having to put any explicit type signatures which can be a little clunky at the use site
>> Absolutely and it definitely helps in our situation where we have a handful of engineers and you know if I'm going back to look at somebody else's code if I don't know what's going on it's gonna be really difficult but if there's a function that's just doubleToNatural I know exactly what's happening yeah that's way easier to read especially in contexts like going through code review where you may not have GHC running for that particular chunk of code you're just looking at it in a web browser and you don't have to guess what type is coming in on the left here and what type is going out on the right it's very from the name what's going on
>> Yeah sometimes that explicitness is really helpful
>> Mm-hmm another area where it's helpful is when you're looking for such a conversion function and I know that we've run into this a lot especially with the time library where well I won't get into time but even for like even for something like doubleToInt I've seen that pop up as a question like on Stack Overflow or here with our team how do I go from this type to that type because if you search in Hoogle which is an amazing thing and can give you a lot of great answers you may not get an answer for that I haven't actually done the search to see but if you search for doubleToInt there aren't many monomorphic functions with that exact type signature but if you know about round ceiling or truncate all this stuff you would know to reach for one of those because those two types match up but by providing this monomorphic version we can have something to hang documentation on and say hey this goes from double to int and it rounds and watch out for this weird even rounding that you may not be familiar with
>> I think it really shows first of all how versatile the prelude can be and secondly how useful it is if you kind of know those base functions because you can turn anything into anything pretty much
>> Mm-hmm yeah it's it's remarkable and in addition to the documentation it can be really useful for people learning Haskell to find one of those monomorphic functions like double to int and then go look at the definition of it and see that it's doubleToInt equals round oh wow I didn't know that they could do that so it kind of teaches somebody while also making the code clearer at the same time which win/win as far as I'm concerned
>> so we've mentioned that we use these functions a lot in our code base to kind of help type inference and help code review and stuff like that one thing we didn't quite mention yet is warnings so we compile everything with the -Weverything flag which is a little pedantic we do turn off some things but GHC will actually warn you when it chooses one of those type defaults that I was talking about earlier so if you have some big computation and in the middle it just decides to use double it'll warn you about that so we use these monomorphic functions to avoid those warnings sometimes to really pin down yeah use this type here I know that's that's what you want to use and we'll say it explicitly
>> and the best part about that is that GHC does warn you so compilers real nice the
>> good friend of ours that GHC and just to kind of give a little color commentary here we went through and looked at how often we use all these functions we've been talking about in our code base and from least popular to most popular they are truncate with exactly one use case floor with also one use case ceiling with two so none of those three get used very much in our code but then you get to round and we've used that 31 times so we reach for round frequently and a lot of the places we reach for that are defining another function so that this count here doesn't include those other functions like doubleToInt it's just round and then going in the other direction we use fromIntegral even more 36 times and again that we define a lot of helper functions with FromIntegral so these are conversion functions that we reach for frequently and we use all the time but it's been nice to do this deep dive on them and really figure out you know how they work and what they mean and some interesting comparisons between them with all these code homophones
>> thankfully we've been able to dive into all of this because of this excellent article written by type classes for those of you that haven't heard about type classes it is run by Chris Martin and Julie Moronuki and it's a great service where they just put out lots of interesting haskell tutorials and kind of walkthroughs i know they've done like how to write a HTTP server basically from scratch which is a really interesting little project to work through so yeah they're cool service you should go to check them out at typeclasses.com they offer subscriptions that are super cheap so check them out similarly we work for a company called ITProTV who also provides training we're more focused on IT skills and we do video based training so you should check us out as well go to itpro.tv as you might guess we use Haskell on the back end so that's why we're here doing a podcast about it yeah I think that about wraps it up Sara any closing thoughts
>> I would definitely recommend to anybody who's listening to this to try out type classes and try us out all this information is very great and you can learn a lot of stuff from both sources so if you're interested in learning here are all the learns
>> for sure all right well thank you for listening to the haskell weekly podcast and we'll see you again next week see you guys