A Thing or Two About Union Types - EMx 194
The panel dives into how different Union Types apply to Elixir. They share their thoughts and experiences on the topic as well as techniques when writing codes. Sascha also gives a brief background about his current project called ExUnion.
Show Notes
The panel dives into how different Union Types apply to Elixir. They share their thoughts and experiences on the topic as well as techniques when writing codes. Sascha also gives a brief background about his current project called ExUnion.
Topics Discussed
- Difference between Product Type and Sum Type
- How are Typespecs used in Elixir
- All about ExUnion and how is it relevant
Sponsors
- Chuck's Resume Template
- Developer Book Club starting with Clean Architecture by Robert C. Martin
- Become a Top 1% Dev with a Top End Devs Membership
Links
Picks
- Adi - SpawnFest 2022
- Allen - OrbitKey
- Sascha - gitmoji | An emoji guide for your commit messages
- Sascha - Domain Modeling Made Functional
Transcript
Sascha_Wolf:
Hey everybody, welcome to another episode of Elixir Mix. This week on the panel we have Alan Weimar.
Allen_Wyma:
Hello?
Sascha_Wolf:
I'm Adi Eingar.
Adi_Iyengar:
Hello.
Sascha_Wolf:
And me, I'm Sasha Wolf. No special guest this week, but we have an exciting topic I've been bugging Adi about for weeks. Like, Adi, come on, I wanna talk about this. I wanna talk about this. No,
Allen_Wyma:
It's Gitmoji, right? Gitmoji.
Sascha_Wolf:
no, no, no. Good motor is my pick for this week. Spoiler! But, yeah. The topic for this week is more of a theoretical one, but with like practical appliances and how that applies to Elixir. And it's some types or tech unions or whatever other term you prefer. But basically there's like sometimes there's product types and we're going to talk about what exactly the difference between those two is and what that means for Elixir and how you usually would model those and why sometimes you might want to do different things. So, um, Adi. I know that you have been playing with Haskell already, so I feel you're the most qualified person here to give an introduction. He's shaking his head. So what, any, what are some types?
Adi_Iyengar:
Yeah, no, Sasha, go for it. You're the one who wrote a library. I am definitely, I could write it, but I don't think I can give a proper definition. So I think
Sascha_Wolf:
Yeah,
Adi_Iyengar:
you're
Sascha_Wolf:
okay,
Adi_Iyengar:
definitely
Sascha_Wolf:
okay.
Adi_Iyengar:
more qualified here.
Sascha_Wolf:
Fair enough, sorry. Sorry if I put you on the spot there. Just didn't want to monologue too long, you know? So yeah, what are some types? What are tech unions or whatever you might wanna call them? So basically, let's start with what's a product type because it's kind of the opposite of a sum type. And a product type is something we're all very familiar with. Struct, for example, Nelixir, it's a product type. Why is it called a product type, you might ask. because the number of possible states a struct can be in is the product of the number of possible states of each individual property. And I'm just gonna give you an example right away. So if you have a struct with two properties, two attributes, and they're meant to be a Boolean, right? A Boolean can have two possible states, true and false. Then the number of possible states of that struct is four. But if you have a third one, the number of possible states is eight. So it's two times two times two. And with a sum type, if you have three different cases, it's the sum of a different case, the number of... Oh, good gosh. I'm losing my train of thought here. So, with a sum type, if you have three different cases of a sum type, it's the sum of the different states of each individual case of a sum type, which... then dictates how many possible states it can be in. Okay, this is like super confusing, I'm realizing that. But basically, if we come back to the example with a struct with three properties, you might be saying, okay, now I have a sum type with three different cases and each can take one Boolean. So basically each case can have two states. Then the number of possible states is for that sum type can be six. So it's two plus two plus two. And in Elixir, we actually have one example of sum types. And that is the OKErrorTouple convention, let's say, let's call it that. You have an OK case and you have an error case. And that's just a convention thing where you say, OK, it can be this tuple or it can be that tuple. You often see it also in type specs. But it's exactly that. It can be even OK with some value or it can be an error with some kind of error message. But it can never be both. It can only be one or the other. If we, for example, had like, I don't know, like a struct which was returned. And it said, OK, it can either have a property that says OK, and it can have a property that says error. In theory, you could fill out both, right? It would be an invalid scenario. But in theory, you could do that, which is why I don't like Go, because Go does that. Go is nil in value for errors and for the actual values. And it gives you both. And I hate that about Go. So sue me. So Alan. Given that I just talked about all of this, how much sense does it make as somebody who hasn't really done it with type definitions and type modeling?
Allen_Wyma:
I mean, the first time we talked about this before the show was it three weeks ago, it made some sense. But maybe it's just been a long day. It's about 10pm over here for me and I haven't left the office yet. So it's just not, it's hard to understand. Like I obviously I know what a product is. I know what sums and and I understand how that makes sense. But at the same time, I don't understand exactly how this works. Like for me, the union types make the most sense. It's like you have You know, like the famous one is maybe, right? Maybe is either something or nothing. That makes a lot of sense to me. Like, I think you and I both looked at Rust. And the way Rust has, like, the result type, the, what's the other one they have? They also have something, also like a maybe type too, I believe, for like
Sascha_Wolf:
Results
Allen_Wyma:
maps.
Sascha_Wolf:
they also have
Allen_Wyma:
Yeah, they have a lot of these things. And that just, like, clicks, you know? So that stuff makes sense. But I'm aware that there's other types out there. And I don't. quite understand how they work and why one would be better than the other or when I would want to use one or the other.
Sascha_Wolf:
Just to be clear, maybe or result from Rust is some type. Those are some types. Because it can
Allen_Wyma:
So
Sascha_Wolf:
either
Allen_Wyma:
a
Sascha_Wolf:
be
Allen_Wyma:
union
Sascha_Wolf:
one
Allen_Wyma:
type
Sascha_Wolf:
thing.
Allen_Wyma:
is a sum type. OK.
Sascha_Wolf:
Yeah, exactly. It's just a different way to phrase it. More like on the academic type modeling thing. And yeah, like I said, in Elixir, you might do the same. For example, if result, right? As you just mentioned in Rust, you have result types where it says, okay, it was successful with some value or it failed with some error, but it can never be both. It can be one or the other. And Elixir is the same with the OKErrorTupel convention. But that is an Elixir more of an emulation of a sum type. It's just a convention. Nobody except if you have dialyzer setup would stop you from returning a FUBATUPEL, right? Nothing in the language would say, OK, this is not allowed. I mean, at the end of the day, Elixir is a dynamically typed language. So you never get quite the same guarantees as in a language like Rust or Dart, for that matter, or whatever, or TypeScript. But still it's only using these conventional things for doing the same kind of approaches. And I'm not sure about you, but I've been to a point in my career, sometimes in elixir code, especially with errors, where I end up, okay, a tuple is just not good enough here, right? Like I don't want to only have a tuple and maybe like then an error message, but I want to have more expressiveness. And what you sometimes do... I've been doing that a few times, is you don't return just an error tuple with a string or whatever, but you return error tuple with a struct inside of that, like an error struct to give it a bit more meaning. But even then, sometimes you might have, not two possible cases, might be three possible cases, where you say, okay, it can be okay, or it can be, I don't know, like continue or skip, or it can be an abort, right? And yeah, you can always go down with tuples, but at some point... If you end up in a situation where you have really multiple properties you might include for different cases, then it just gets messy. It just gets messy very quickly. And that is where I feel Elixir is a language could benefit from a bit more developer support, let's call it that, right? Where you have a bit more support from the language itself to give you something to model these more complex cases. And this is actually born out of a project I've been working on for a while now. So like a kind of a side project at least. So I'm doing a side project, but I'm barely doing it. It's not even like, it's more like a year in the Asian phase. But in there, basically I wanted to model, I wanted to build a database of games. So because I wanted to basically model, okay, which person has which game. And that includes where do they have that game? So do I have it on Steam? Do I have it on Epic? Do I have it on Xbox? Whatever, right? And that means you might have a list of ownership contexts. But each possible case is one case of this ownership idea. It might be on Steam, and then it had different properties. It might be on Epic, but then the different properties, it might be on PlayStation, and it has different properties. But still, it's like, where do you own this game? Where did you get it from? And that was exactly the point where I was like, man, I could now sit down and write down a bunch of structs. fields and blah blah blah blah blah and i've done that in the past there was one project where we built like a HTTP kind of thing that could either do a get request or a post request and then i actually modeled that out okay have like a get request struct and have a post request struct and it can be one of the both but it gets very cumbersome very quickly because it feels super boilerplate here that point it actually felt like i was writing java because i had to write out so much code just to get to this point of hey, this is a structure of these fields, and yes, I want to annotate them with a bit of types, but it gets very bloated very quickly. And that is exactly the point where I feel Elixir as a language could be more support coming from it, because just writing these sum types and something like Rust feels super convenient. And the same also for something like Haskell or Gleam or Elm or whatever, right? Any... language which has more stronger support for those, especially if they have compile time support. I mean, has any of you ever dealt with Elm? I think also Rust does it, right? Like where you add a case to one of the tech unions to the sum types and the compiler is like,
Allen_Wyma:
Yep.
Sascha_Wolf:
hey, you forgot to match it over there. That's just...
Allen_Wyma:
No,
Adi_Iyengar:
Yeah.
Allen_Wyma:
the first time you do it, you're like, damn, this is so annoying. I don't want to do this. And then after you do it, and you realize the benefits that you're like, basically elm was known as like the front end framework that would never crash because
Sascha_Wolf:
Yeah, it's a kind
Allen_Wyma:
of
Sascha_Wolf:
of
Allen_Wyma:
this.
Sascha_Wolf:
tagline, yeah.
Allen_Wyma:
Yeah. And actually, did you hear it? Did I'm crashing one day?
Sascha_Wolf:
Yeah, but there was a crash. I've heard things
Allen_Wyma:
because
Sascha_Wolf:
about
Allen_Wyma:
they left
Sascha_Wolf:
sometimes
Allen_Wyma:
a debug
Sascha_Wolf:
being
Allen_Wyma:
line
Sascha_Wolf:
crashes.
Allen_Wyma:
in there that makes the program crash. But in any case, like, you wonder like, why is it? How come this thing has this guarantee? And then you're like, Oh, it's because they forced you to do this thing where you have to handle every situation. And then once you have that, you're like, damn, this is really nice. First, it's like, damn, this is annoying. You know, it's like everybody fights the bar checker and rust, right? And then once you get beaten by it enough times, and you start to realize it, you basically get Stockholm syndrome, right? You're like, Yes, I do want to handle everything. I do like this. But you actually do like it deep down because you know the benefits are much more than the the what do you call it? I want to say the seance, but the the rules
Adi_Iyengar:
effort.
Allen_Wyma:
or
Sascha_Wolf:
the
Allen_Wyma:
the effort. No, there's
Sascha_Wolf:
cost,
Allen_Wyma:
another word like
Sascha_Wolf:
the
Allen_Wyma:
yeah
Sascha_Wolf:
cost.
Allen_Wyma:
maybe but there's another word I'm thinking about. It's like that anyways call it whatever
Sascha_Wolf:
gains.
Allen_Wyma:
you want.
Sascha_Wolf:
Yeah,
Allen_Wyma:
It'll
Sascha_Wolf:
but
Allen_Wyma:
probably come to me later on but yeah.
Sascha_Wolf:
I definitely get what you're saying. And it's like, it's maybe my same experience with some of these more strict languages. And my personal opinion is personal theory is that it comes from a place when you are used to manage that inside of your head, right? Like when when you program and then, for example, okay, I need to do need to remember to update it over there. If I add this another case, but all of this is before that managed by your brain And then suddenly this piece of technology comes along and says, like, yeah, I'm going to take care of that for you, but you need to follow my rules. You're like, no, my rules have been working for me. Go away. And that is this episode, this period of friction, right? But at some point you realized, Hey, wait, if this thing is taken care of it for me, that just means I no longer need to think about it. And like, if you get to that point, it just, it gets so nice because suddenly this part of your brain, which is always keeping track of it, it's just like, Hey, I can relax now. Nice. Right. And then you just, you try, you embrace it. And then you come to a point where you actually do enjoy the compiler yelling at you because you're like, yeah, okay. How would have missed that? Thank you. Thank you Mr. compiler. And then theory dialyzer could be the same, but, um, hasn't happened for me yet. So Adi, what, what is your experience with the first kind of things? Because somebody said earlier, you've added that little bit of a Haskell where you can write them, but.
Adi_Iyengar:
Right.
Sascha_Wolf:
Maybe
Adi_Iyengar:
Yeah, I
Sascha_Wolf:
with
Adi_Iyengar:
mean,
Sascha_Wolf:
a perspective.
Adi_Iyengar:
it was, yeah, the first, I mean, exactly what Alan said. I mean, first time I did Haskell, it was annoying because the effort to get something basic was huge. And I mean, it's like, I would say the restriction, the compile time restrictions are like, I mean, if you compare the difference between Rust and Elixir, double that, and that's a difference between Haskell and Elixir. So it was quite annoying, but. I mean, once, like, again, Alan said, once you're used to that kind of way of thinking, it's almost like therapeutic to do it. There was a time when I would do Haskell mornings instead of meditation and just do Haskell to get momentum for the day. I have my thoughts have evolved a little bit that I generally like to use types and union types now when there is a more complex problem that I'm trying to solve. And I mean,
Sascha_Wolf:
Yeah, definitely.
Adi_Iyengar:
generally, You know, generally in simple ones, even if I am used to it, it takes a mindset for others to like incorporate that and like you use that it's a lot of effort from others and like onboarding gets harder, making changes, making simple changes gets harder. So, generally my you know point where things start to get complex is like you know when we're doing like NP complex problems. That's when types especially help a lot. I think I've given this example a few times. One of the, actually the first NP complex problem that I saw that was in production was a very complex tax calculation for healthcare in the US, which you guys might know is terrible. And that, doing it in Elixir, we, it took like months to write that code, right? And we had, we added property-based tests, kind of like, capture the correctness of the program. So we're running the test for five hours or something. We're getting like 95% correctness of the elixir. After I left the company, just for a month, I had a break. And I wrote that in Haskell. Took me three weeks. And the correctness was 100%. Because every edge case, I could define a type in the function. So at
Sascha_Wolf:
Mm-hmm.
Adi_Iyengar:
compile time, I could capture all of the edge cases and force the app to behave in a specific way. I think that's super powerful. That's where I firsthand experienced the advantage of having a very robust, explicit type system. And it was faster to write, too, given that you're already used to that kind of thinking. But yeah, it's very similar to you guys' experience.
Sascha_Wolf:
Yeah, I can definitely relate to that. Um, there's also an amazing book from Scott Blushen, which is called domain modeling made functional. And in that book, he's using F sharp, which is not necessarily my weapon of choice. Right. I mean, I do really enjoy working with Elixir, but what he basically does and that it's just, I was mind blown when I read that is he didn't write a single line of like working code, but he basically threw out about half of a book, he was just specifying types, just types. to model the domain in that case. And I mean, the classic domain-driven design example is with like, you have some kind of sales flow and you might have an invoice, but you have like an unpaid invoice. And then at some point you have a paid invoice, but everything is still an invoice. It's just different states of an invoice. And that you modeled with a union, with like a sum type, right? And then said, okay, depending on what kind of state that invoice is in, that there is different properties that are potentially relevant. And that just, whenever I was like... BOOM! That makes so much sense!
Adi_Iyengar:
Yeah.
Sascha_Wolf:
And that is something, I feel like unions and some types, that's just a tool in the tool belt, when you really crock them, you start to see them everywhere. It's kind of the same with sets or hashes or arrays, right? Where specific problems for a set are super useful, not a private problem, but it's good to have it in your tool belt. And the union is exactly also that. It's a good tool for modeling specific types of problems.
Adi_Iyengar:
Yeah, I think you mentioned that book as a pick a couple times, if I'm not mistaken, because it's on my read list that was inspired by your picks. So yeah, I was just checking that. But I think one of the things I really like about F-Sharp, I haven't done it in years, but they have something called railway-oriented programming, which is making sure it's kind of like the OK error tuple, but expanded to user-facing results. That really goes so well with union types to make sure everything is captured, but not only captured, but handled. It's a perfect example of how you can build a deterministic system. I mean, F-sharp has a lot of other things that didn't. There's many reasons why it didn't blow up. But I think this was a very good concept. I remember thinking it's a great use of union types to make sure you write an error-free app from an end user perspective.
Sascha_Wolf:
So I think there is even, there's like a library in Elixir, which is kind of trying to get some of that goodness specifically from Haskell, right? In Elixir there's Witchcraft, which I always love the name. I've always loved the name of the thing because you do use Witchcraft, which is just amazing. But it tastes like this child library, let's call it that. I'm not sure how to specifically refer to that, but LLJ, which is for algebraic data types. which is also where some types and product types and the whole, like this notion, this terminology comes from, because it is actually at that point closely related to mathematics. And, um, the thing is like, while I really enjoy this being there and the community dabbling with that and people using it, it's like, it's tough thing to swallow, like it's not just something you take one peek at and you're like, Oh yeah, sure. I get it. Easy peasy. Right. But it brings some of that. how do I say like upfront investment complexity mental up
Adi_Iyengar:
All right.
Sascha_Wolf:
like the kind of brain juice you need to invest before you really grok that what this thing does this this kind of brings along with a word from Haskell and like I definitely can relate to what you said earlier with like Adi with Haskell and the first time you're like what the heck is happening I also dabbled a few times with Haskell and I'm feeling at a point where I could probably write a halfway complex thing man, I would probably be completely helpless if I couldn't Google every five seconds.
Adi_Iyengar:
You mean, Hoogl.
Sascha_Wolf:
Yeah, exactly. So yeah, I really love that there is this project and I really think it's a cool thing also to push the community and more towards some of these very, really stricter but powerful modeling ideas. But it's not something you can just bring into every project and use right away because people will be confused. So I'm kind of curious, like given all of that has been said now and your experience with Rust, have you ever been in a situation where you kind of missed that modeling in Elixir or you kind of deliberately went for something similar? And how did you do that?
Allen_Wyma:
I'm kind of the person that tries to write code in the way that that language wants you to write it right like a pythonic way idiomatic rust I don't know what you call elixir what if you're writing is it just idiomatic elixir is there any special phrase no
Sascha_Wolf:
Yeah, idiomatic I think is the best word
Allen_Wyma:
Yeah.
Sascha_Wolf:
you can be using here.
Allen_Wyma:
Yeah. So I try to go that way. Right. Um, so I mean, you
Sascha_Wolf:
Elixiri
Allen_Wyma:
can't,
Sascha_Wolf:
maybe.
Allen_Wyma:
I want to say alchemist, alchemist, elixir or something. But, uh, no, I mean, you cannot like, I mean, I, I guess you can take a screwdriver and beat a nail into the wood like a hammer, but it would kind of defeat the purpose. Right. Um, I think The only thing that I do do recently, I just started this recently. Of course, I always do like my testing first, and I kind of use my test to drive my API's. And then I of course, use a test and to make sure that those API's work properly. When I say API's I'm talking about like, you know, when I write a function, I want to pass in what and I want to get what out of it. And then I do the guts of that function. I think the only other different thing I do now And maybe it's because I have been working more with type languages like Dart and Rust, is actually writing the spec, the type spec above it. I think that's the only difference that I do recently. And I start to think about what would that be? I never run a dialyzer. I did before. I don't remember running it right now. But it's good that the type specs are there, because you can output those in a documentation. So
Sascha_Wolf:
Mm-hmm.
Allen_Wyma:
that's probably the most helpful. And I do force other people around me to put in type specs just because I want people to know what should be going in and what should be coming out. Yeah, so I guess that's kind of like maybe it's a long answer for your question. Is it affecting your code? Yeah, maybe it is. I think that's kind of where the type specing kind of came in at because I don't use it for anything other than just knowing what am I getting and what am I getting out of it.
Sascha_Wolf:
Yeah, I'm kind of the same at this point. We talked about it a bit in the RSC ICD episode, right? That type specs and then like private code, I tend to add them to top level modules for documentation purposes, but I no longer integrate Dialyze at that point. But for library code, I still do because I don't want other people's code to break because I fucked up my type specs. But yeah, for documentation, I think they're amazing because they do definitely add value to... what's a generated documentation because you can easily access them there. They also tend to be available through like code completion, right? And that is just, that is just something which, which can be very helpful to work with. Ari is like wiggling his head. Why are you wiggling your head Ari?
Adi_Iyengar:
I know I agree. I agree with most of what you guys are saying. I think the type spec, there is still, I think there is a tool missing in Elixir to make it useful right now. And I feel like something like, I mean, getting a compile time warning sure is great, but I don't think Elixir is built for that. I think I would say something like if you have type spec, like a property-based test. based on type spec that
Sascha_Wolf:
Yeah,
Adi_Iyengar:
generates
Sascha_Wolf:
that would be great.
Adi_Iyengar:
data and expects the result to match that type spec might work, might be more successful in Elixir. But yeah, but other than that, for the most part, I agree with you guys.
Sascha_Wolf:
There's also still the Gradualizer project going on, but that has more velocity in the Eulon community. I think there is some effort also to make it available for Elixir. But for everybody who have never heard of that before, I mean, we did mention in the past episode a few times, but basically it takes the type specs as they are and interprets them in a more strict manner. So because Stylizer is like very lenient when it comes to false positives, kind of, so it really doesn't want to give you false positives, which makes sense when you consider like type specs were added at a later point to like existing code and then you really don't want to have a tool which is screaming at you over time for things which are not worth screaming about. But it does reduce the usefulness of type specs and Gradualizer is basically taking the same type specs, redefining says, okay, I'm more strict about this. And from what I've heard, it is quite usable in the Erlang ecosphere, not really yet for Elixir. So maybe that is an interesting thing to look out for. And I mean, from what I've heard, also the core team is now again looking into how Elixir could potentially be more statically typed, right? There is like literally research going on, like I think... some funding for some research in that direction. I still saw a slide from a few years back, I don't know exactly what it was, in one elixirconf where there was a slide from Jose where it said we started working on a type system. The next slide was we stopped working on a type system, which highlights the complexity of the problem I feel.
Allen_Wyma:
But also for even for WhatsApp, right? This is the second type system they tried to add to Erlang, right? Or am I remembering wrong?
Sascha_Wolf:
The sec- I mean there is motion going on, the taped Erlang thing is on ice now, but
Allen_Wyma:
Yeah, so
Sascha_Wolf:
there's
Allen_Wyma:
typed
Sascha_Wolf:
something
Allen_Wyma:
Erlang
Sascha_Wolf:
else.
Allen_Wyma:
is the first one I would call and then this would be the second one, which I don't understand exactly what the differences other than it's different. The
Sascha_Wolf:
I mean,
Allen_Wyma:
edit
Sascha_Wolf:
I
Allen_Wyma:
the
Sascha_Wolf:
mean...
Allen_Wyma:
edit. What's it called? What does it like to add Scala to it, which is I'm still trying to figure
Sascha_Wolf:
Yeah,
Allen_Wyma:
that part out.
Sascha_Wolf:
it's a bit odd. But yeah, maybe to kind of go full circle now. So given that we talked about all of this, right? Like with the lack of expressiveness with tuples sometimes, but with a boilerplate that's necessary to write a bunch of structs, for example. I've written a library to tackle exactly this problem. And I call it XUnion. It's not yet published at the moment of recording. I do hope it's published when we actually release this episode, but what it basically does is it generates from opinionated but concise DSL, a bunch of structs and type specifications and helper functions. To do just that, we have like, okay, I have different cases for this type, best cases maybe, right? Like I said, this is a maybe example. So I have a sum case and I have a none case. Sum value and I want no value. And it generates, in this case, you could model it, write it out, sum with some value, pipe, none. And then it generates a sum struct, it generates a none struct, it generates some dev delegate functions, so sum and none to create those very quickly. And it also, which I think is kind of the nicest little detail about it, it generates a guard, is maybe. which checks if it's one of those two cases. So it gives you some usefulness to also enforce. Hey, this really should be one of these union type values. I don't care which one, but it should be one of those. And it has, like I said, it has been born out of my own kind of itch because I'm working on this little project of mine where I do want to model those things. And I did... looked around and I found algae and I knew about it before and I found some other projects which kind of did the same thing, but either they were not maintained on a really like in a super rough prototype stage and abandoned, or they were something like algae, which was just bringing in more than what I asked for. So here's this thing now, which takes the idea and it's really only focused on that. It's only focused on giving you like a short opinionated concise way to define a bunch of basically structs. to model something which is a bit more complicated than just OK or error.
Allen_Wyma:
The most important thing is that you're using gitmoji to when you're committing.
Sascha_Wolf:
I'm sorry. Yeah, before we hit record, Eddie took a look at the repository. I was like, oh man, so you also started using emojis in your commit messages? It's a bit of a pet peeve of mine. So yeah, but yeah, that thing exists now. So I would say go check it out on GitHub. Happy to give me some feedback on Twitter or whatever. But I would actually be interested to hear your two opinions on it, Al and Addy, because I mean, you kind of have different perspectives on this topic, right? So this is something you'll be using if not why, or if yes, why, right?
Allen_Wyma:
I would be interested to try it out to see how it works, but off the top of my head, I mean, the way I think about Elixir is it just doesn't work the same as languages that have this as a first class support, right? So I'm not too sure. You know, I mean, I can't lean on it, right? Like I wouldn't Rust or
Sascha_Wolf:
that is true.
Allen_Wyma:
other languages that do it like Elm. But I mean, I think it definitely like we talk about using. type specs as like they did in the domain modeling book, right? Where they modeled
Sascha_Wolf:
Mm-hmm.
Allen_Wyma:
everything. I mean, you could still do that with with this, at least maybe you can't lean on it, but you can at least model stuff with it, which is definitely useful. I'm looking to Adi, but Adi looks like he has something brewing.
Adi_Iyengar:
Oh no, I'm trying to think where I would use this. I mean, I would agree with Alan quite a bit here. I think maybe, again, the way Elixir, I write it over the way, I think it's supposed to be written, does diminish the need for this a little bit. But I think errors is a good example, Sasha, that you mentioned. I think at least the pattern in which we do errors, especially in the web layer in Phoenix, Adding some kind of a sum type for that would be useful. Yeah, the way we get around it is with a protocol and implementation right now with our error types. Because we have 100% code coverage, our test breaks if an implementation isn't defined for that error. But yeah, I'm again trying to think where would be a good... use for this in Elixir.
Sascha_Wolf:
Yes, for me, it was mostly this scenario of actually wanting to model different cases of a thing. Right. Like coming back to an example earlier with games and also having potentially different properties for that. And it just, I really was like, okay, I can't do tuples here. Like it's too, it's too complex to do this with tuples because I definitely have different properties with different names and I do want
Adi_Iyengar:
Mm-hmm.
Sascha_Wolf:
some compile time enforcement of them. Right. And once, for example, say, okay. for the Steam where my different properties are relevant and for Xbox or PlayStation. And yeah, I could have a tuple with like Steam at them, but then again, like nobody would stop me from having a typo which says, ZEAM. And that was where I came from. Yeah, I want a bit more guarantees by the compiler. Yes, I can't go 100% there to say I want to make sure that I always catch every different clause,
Adi_Iyengar:
Right.
Sascha_Wolf:
but at least for the creation and for like documentation purposes, it's way more. uh information rich but
Adi_Iyengar:
I think documentation is a great point, actually. I think documentation would be great, like API documentation or something. There was this project that I was doing last year with a previous co-worker of mine, Jeffrey Matias. He wrote Testing Elixir. He came up with the idea to use our test suite, controller test, to generate API documentation, because the tests were so well done. And he needs some metaprogramming, so he reached out to me and we just like coding. But I think the lack of determinism in all the different responses, errors and all that, made it significantly harder than it needed to be. And I think that's where the structure, I guess that's kind of what we're getting at, right Sasha? Structure would help us determine that, yeah.
Sascha_Wolf:
Yeah, and also like I mean, like I said earlier, I've have written similar code in the past with like multiple structs. And in this case, it was just like kind of this request modeling for a get request or post request. But that module, I mean, it has also some inline documentation, some module documentation, right? But that module is over 200 lines long, because it's just, I need to define the other structs. I need to specify the fields. I need to specify the types for the fields in a different manner. I need to add the same functions to create them and maybe also enforce a little bit of, hey, this field ought to be that value, right? I need to have some handling at the top level where like, okay, this is like, we see the different types, this is how you're meant to use it, right? And all of this is possible, but the information density of all these number of lines of code is relatively low. It's like a few critical places you might wanna look at, but most of it is boilerplate. And... That is exactly why I said I could do the same, for example, in this like little pet project of mine. But I don't want to write all of this out. So it was a bit of a born out of a progresive nation kind of, hey, what if I write a library which generates that code for me? But the more I played with it, I kind of started with like, what was this first idea? So I played a little bit of it. Like, could I do this? Like, how could I do this? And I ended up with something I feel is just, if you have a specific use case, I have these different cases, and I want to lay out those other possible scenarios, right? And those are the fields you have, and those are the types of fields you have. But I don't wanna deal with writing all of the struct boilerplate stuff. Then it just scratches that edge really nicely. Maybe I'm just the only guy who will ever use it, but I even, but the nice thing about what I've ended up with here, is I feel it's very self-descriptive. So even if you never... really looked at that library before, it makes sense. Like as soon as you saw like one union type, you look at it and like, yeah, okay, this makes sense. I get it. And I feel that there's a lot of value in that, which like I said earlier, I write like something like LJ also kinda makes sense, but it does more, the unit need to grok it a little bit more to figure out what exactly it does.
Adi_Iyengar:
Yeah, totally. I think that's definitely what you have going. I'm a lot more likely to give what you have a try than algae. I mean, the whole algebra of the types is just too crazy right now.
Sascha_Wolf:
I mean,
Adi_Iyengar:
Oh.
Sascha_Wolf:
I really like, in general, a lot of the goodness of computer science has also come from people doing crazy
Adi_Iyengar:
Right,
Sascha_Wolf:
things, right?
Adi_Iyengar:
right, right.
Sascha_Wolf:
And it has been leaked more, for and so forth, into mainstream languages. And I think that's amazing. It's just not for everybody. Like, I would never get the idea of using something like LG in a bigger corporate kind of project, even though it might be useful, right? Because then I'll always be the guy who used that weird library nobody understands.
Adi_Iyengar:
Right. Right. Like I said earlier, at that point, I would just use actual type language only for that
Sascha_Wolf:
Yeah.
Adi_Iyengar:
subdomain
Sascha_Wolf:
Yeah.
Adi_Iyengar:
of problem. I think I do fall mostly in Alan's category, where I don't think Elixir, at least at this point, is supposed to be used in at least as extreme a way as like Witchcraft and algae. It's just... I mean, I looked at the README and knowing Haskell... sort of made sense, still not completely. I still had to look at the code and examples more. And yours is like, I can see someone who doesn't even know Haskell or just barely knows union types or disjoint types, whatever. They can read through the readme and it makes sense to them. So I think that's definitely what you have going here for XUnion.
Sascha_Wolf:
So yeah, that being said, this thing is now out. Like I said, I would definitely want to make sure to get it published before this episode releases. I'm probably also gonna write some type of blog post. I'm not good with blog posts, but I'm better with talking to be honest, to get a bit of awareness on this thing because I would really love to get some feedback from people if they see the value and what they would like to use it for. And I'm definitely gonna bug some people at work about it. Like, hey, hey. I thought you want to use this library, it's amazing. But I'm starting to see a theme in the libraries I'm writing. They're always like, this is one problem, and this is one focused solution to it, and it does nothing else.
Adi_Iyengar:
And that's the point of libraries, right? Solve a very specific problem. That's how you develop. I mean, if it solves a specific problem that no other library solves in that as good a way, then that's the point of library. I have a piece of feedback for you. Line 29 of x-unit definition type field is not tested. The only line in your library that's not tested.
Sascha_Wolf:
I'm not surprised that you bring that up, Ari. It's probably on, yeah, I see on coveralls, right? Yeah, yeah, yeah. 89% code coverage and this guy gives me shit.
Adi_Iyengar:
Wait, it's 98.
Sascha_Wolf:
98, yeah, I
Adi_Iyengar:
Yeah.
Sascha_Wolf:
said, sorry, 98, 98% code coverage and Ari is giving me shit for it. Okay. So any last words people you would like to talk about anything you think it's still worth covering?
Allen_Wyma:
I want to know how come your test coverage went down you wrote 100% and then went down to 98.28% what happened?
Sascha_Wolf:
I don't know. It
Allen_Wyma:
I need to check
Sascha_Wolf:
was
Allen_Wyma:
this
Sascha_Wolf:
probably
Allen_Wyma:
out.
Sascha_Wolf:
my last commit. Okay, I changed
Allen_Wyma:
Yeah.
Sascha_Wolf:
some things, so I'm gonna have to double check on that.
Allen_Wyma:
I'm just a little bit
Sascha_Wolf:
Yeah,
Allen_Wyma:
shocked.
Sascha_Wolf:
it used to be 100%. Nah, nah, we'll see. I mean, it's
Allen_Wyma:
Oh
Sascha_Wolf:
still
Allen_Wyma:
yeah,
Sascha_Wolf:
inactive.
Allen_Wyma:
your definition.
Sascha_Wolf:
As of right now, the readme is like half finished. So, and I still dealt with it yesterday. Yeah. Yes, there's some little things I'd like to tackle here. For example, at the moment, you can't really do protocol deriving or like decorating the generated structs. I have an idea of how, but how you could do that. But I thought, Hey, This already, at least for me, it brings value. So when I push it out, I want to make it available to everybody and yeah, get some feedback early. I think that's also not a bad idea, but I was like, I had really hard time. Fun fact, finding a good example for adding types to the union definition. I'm really happy with what I ended up with because I basically, in the documentation, I have an example of like defining a color. type, which can be X, it can be RGB, it can be RGBR, ASL, ASR. But I, when I first like, oh, what's a good example, which isn't just, I don't know, pulled out of my ass, right? Because my experience, like code like this or documentation like this tends to have very contrived examples. I mean, the example before that with circles, square and rectangle is kind of contrived again, but I was really
Adi_Iyengar:
I think
Sascha_Wolf:
happy
Adi_Iyengar:
error
Sascha_Wolf:
with
Adi_Iyengar:
tuple. Yeah,
Sascha_Wolf:
ArrowTruple. Yeah, ArrowTruple.
Adi_Iyengar:
that's an actual problem that I can find. That's the actual problem where I see the best fit for this. That happens often enough. And yeah, I mean, there's so many. Every big Phoenix project I've worked in, there have been errors that haven't been well handled at the web layer. Not only something that this would be a very
Sascha_Wolf:
Yeah, yeah.
Adi_Iyengar:
good use case for that.
Sascha_Wolf:
Yeah, I'm kind of curious to see where the journey will be going. I'm definitely going to keep working on this because it's small enough to make some changes here and there. It fits my personal schedule, but just probably
Adi_Iyengar:
Yeah.
Sascha_Wolf:
also why I'm doing these smaller libraries. They are something I can reasonably finish in a weekend or two.
Adi_Iyengar:
Yeah.
Allen_Wyma:
Just
Adi_Iyengar:
I can
Allen_Wyma:
make
Adi_Iyengar:
see
Allen_Wyma:
sure
Adi_Iyengar:
this
Allen_Wyma:
you
Adi_Iyengar:
being.
Allen_Wyma:
don't do 100% test coverage just to buck Audi. Sorry, just wanted to
Sascha_Wolf:
Yeah,
Allen_Wyma:
say
Sascha_Wolf:
I'm
Allen_Wyma:
that.
Sascha_Wolf:
gonna do 99.5.
Adi_Iyengar:
I think one place I can actually, one thing I can see this again being coupled really well with is the action, that's what it's called, the fallback controller, right? I think the handling of errors and the fallback controller, you don't have to do the protocol implementation stuff at that point. So yeah, sorry, I'm just thinking out loud where I can use this.
Sascha_Wolf:
I mean,
Adi_Iyengar:
I want to support this and I want to use it.
Sascha_Wolf:
I mean, okay, you could, for example, the forbic say, hey, kind of one definition of a forbic handle where it says with regard, hey, is it one of those cases, right? And then
Adi_Iyengar:
Yeah,
Sascha_Wolf:
you
Adi_Iyengar:
exactly.
Sascha_Wolf:
could pass that to a different piece of code. So yeah, like I said, I'm pretty happy with what I ended up here. Also with like the convenience it gives you, like with regard, that's not something I've never wrote for the other stuff before, because it was just too fiddly.
Adi_Iyengar:
Right?
Sascha_Wolf:
But you get all of this for free. So as soon as you put on the definition and it generates all of this code for you. So I'm pretty, pretty happy with what I ended up with.
Adi_Iyengar:
I think another cool thing, again, I might just be thinking out loud and completely off of where you want to go. It might be somewhat turning the sum types into a kind of a product type. It's like being able to match on the unions as a whole. So I have a union struct, which can match any of the types in a given union. Again, that kind of takes into account pattern matching as well.
Sascha_Wolf:
That's kind of the way the God is coming in.
Adi_Iyengar:
Oh that's how you implement it guard, gotcha.
Sascha_Wolf:
Yeah, if you get a value, you can say, is it one of cases? It basically checks
Adi_Iyengar:
Oh, right,
Sascha_Wolf:
if it's
Adi_Iyengar:
right,
Sascha_Wolf:
one
Adi_Iyengar:
right,
Sascha_Wolf:
of the
Adi_Iyengar:
because
Sascha_Wolf:
stuck
Adi_Iyengar:
it defined
Sascha_Wolf:
cases.
Adi_Iyengar:
the mod. Right, got it. Yep, makes sense. Because
Sascha_Wolf:
Yep.
Adi_Iyengar:
then pattern matching will work too. Very cool.
Sascha_Wolf:
Yeah, that's kind of the whole idea. Okay. Then I would say let's cut it short. I mean, the short episode has never hurt anybody. Right. I definitely I'm on the side for some podcasts where I do enjoy a short episode occasionally. So let's go to picks. And I'm just going to go ahead because I already said what I will pick. Just to spite Adi a bit. I'm going to pick Gitmoji and Gitmoji is a bunch of convention. And there's a website for it. You can visit it and it basically specifies, hey, for this type of change, you can prefix your commit message with this emoji. And we actually had a guest a while ago who also picked it and since then I've fallen in love with it. And I've also introduced it to at work in our team in the backend squad where we started to use it collectively. And it's for scanning commit messages, it's just the best. To be honest, like I mean, you said earlier, you could also use keywords. Uh, I did kind of do that in the past, but. Like, I don't know, like my, my brain with like emoji, my brain passes emojis faster than a keyword. So to be able to see, okay, yeah, this is just a bunch of refactoring, effectoring, effectoring. Okay. This is some feature. Wop, wop, wop. And it's really, as soon as you kind of get, get the, get the hang of it, like what the different emojis mean, it's, it's really nice to scan, for example, pull requests for like the kind of work that happened there. So. It's not for everybody. I totally get it. I definitely do enjoy it. And just to, I think I've picked it already twice, but since we all mentioned it, I'm just gonna repick Scott Blushen's Domain Modeling Made Functional book, because I do think it's really worth a read. It captures, even if you're not super into domain-driven design, it's still, it's crazy to see how much work can be done upfront with type definitions without even writing a single line of. executable code. So even for that, it's already worth a read. Okay, Adi, what are your picks for this week?
Adi_Iyengar:
Um, I don't think I have any. Oh, well, yeah. Well, it's a little too late. And probably this episode will be published after it. But SpawnFest is happening this weekend. So it's not a pick. But maybe go check the website and all the repos that come out of it. It's like a retro pick.
Sascha_Wolf:
Are you going to participate?
Adi_Iyengar:
I want to but I am just short of time. I don't think I'll be able to spare 48 hours unfortunately. I want to and I wanted to use Gleam but oh well.
Sascha_Wolf:
Ah, okay. I would have expected to see what came out of that.
Adi_Iyengar:
Yeah, I had a idea about, it was like a vote, when you vote, the effectiveness of your vote based on what party you're voting for, what are your issues and what state you're in, because we have the delegate system. So it was, yeah, anyway.
Sascha_Wolf:
Okay, Alan, what are your picks? Rustbook, pick up Rustbook, come on.
Allen_Wyma:
Uh, yeah, I just had a time looking to Russ recently, but I want to, I need to for something, but, uh, I want to say I, I picked up this thing called a orbit key. Have you guys heard this thing before? So it's a company called Orbitkey. They start off making this thing called a key organizer. So one of the most annoying things for me is when you got your keys in your pocket and it scratches up all your stuff. I don't know about you guys, but you guys look like you put your keys in one pocket and leave it alone, right? All right, so I put my keys in the pocket and I put something else in there and it just scratches the hell out of everything. And so I heard about this thing called the Orbitkey. I'm super happy with it. Like... I put like about seven keys on there. They kind of come out like this. It's got like a little sheath on it. It's kind of like a little cool thing I use now. Uh, of course I always have a bottle opener just in case, you know, you got to have a beer once in a while. Yeah. You know, I'm talking about Sasha. Uh, yeah. So is that your ring is a bottle opener.
Sascha_Wolf:
Yes,
Allen_Wyma:
That's awesome.
Sascha_Wolf:
I have a bottle opener ring.
Allen_Wyma:
So then you're more crazy than me.
Sascha_Wolf:
It's a great icebreaker on any party, seriously. Like if people come
Allen_Wyma:
Yeah.
Sascha_Wolf:
along and ask somebody, does somebody have a bottle opener? And you're like, yes, sh-
Allen_Wyma:
I'm sorry.
Sascha_Wolf:
Bop! And the bottle is open, right? And they're like, how did you do that? Did you do it with your hand?
Allen_Wyma:
Yeah, that's what you shouldn't tell them that you have a bottle opener. Just do it with your hand and see, you know.
Sascha_Wolf:
Thanks for watching!
Allen_Wyma:
Anyways, this thing has been awesome. It's been cool to put in my pocket and not to worry about anything getting scratched up. So it's kind of nice. So, yeah, that's my pick for this.
Sascha_Wolf:
Yeah, nice. I tend to just keep my keys in one pocket and put a bag of... Good God, I don't know the English word for that. So like, when you blow your nose, what is the word for that?
Allen_Wyma:
Oh, tissue.
Sascha_Wolf:
Tissue, thank you. Tissue, yeah.
Allen_Wyma:
I think I said bag or something else. I
Sascha_Wolf:
No,
Allen_Wyma:
was
Sascha_Wolf:
like,
Allen_Wyma:
a little bit confused.
Sascha_Wolf:
I put a package of tissues alongside and
Allen_Wyma:
Yeah.
Sascha_Wolf:
then my pocket is full. And I mean, my excuse, I'm not gonna scratch the pack of tissues, right? Like, who
Allen_Wyma:
But
Sascha_Wolf:
the fuck cares? So,
Allen_Wyma:
yeah,
Sascha_Wolf:
yeah.
Allen_Wyma:
but that's why you do it just because of that reason, right? Because otherwise
Sascha_Wolf:
Yeah,
Allen_Wyma:
your
Sascha_Wolf:
that
Allen_Wyma:
key
Sascha_Wolf:
is true.
Allen_Wyma:
scratch. That's why I picked this thing up. It's been, uh, it's been interesting.
Adi_Iyengar:
My keys scratch my car keys because they're in the same keychain. So, yep, I feel you, Alan.
Allen_Wyma:
So that's why it's my pick.
Sascha_Wolf:
What? Oh, Addy, do we want to do like a little bonus round of video game picks? I have a video game pick.
Adi_Iyengar:
Yeah, go for it. I'll think of one.
Sascha_Wolf:
Because I recently actually got a PS5. I got one here. So I've been
Adi_Iyengar:
Finally.
Sascha_Wolf:
playing... Yeah, yeah, yeah. It's still hard to get in Europe. It's still hard to get in Europe. And I've been playing Returnal on it. And I'm really enjoying it. Like I get some criticism out there which says that the runs in Returnal are too long. It feels really harsh when you lose a run because they can easily be two hours. But man, the gameplay is nice. I'm really enjoying the gameplay. And I'm also really enjoying the fucking controller. The controller is just... I wouldn't have expected it before, but my gosh, this is a nice controller. So, yeah, definitely worth the investment. I've been really happy with the purchase and I'm really enjoying Returnal so far. So that's my bonus pick.
Adi_Iyengar:
Yeah, plus one for Eternal. I played it when PS5 came out and I was hooked. It's such a good game. I'm actually, well, not anymore, but last month, I started replaying Sekiro. Didn't get a chance to get back to it, but it's just such an amazing game, and it looks amazing on PS5. If someone has a PS5, it looks amazing on the new gen consoles. And yeah, if you're going for punishment, but not as much punishment as Dark Souls 3. But like all return all for that matter, but you want to still have some fun. Sekiro is good
Sascha_Wolf:
Nice. Okay, then thanks for tuning in folks and I hope you learned something today and I hope my rambling in the beginning wasn't too confusing and see you next time when we have another episode of Elixir Mix. And Adi, you now go take a nap. Bye bye!
A Thing or Two About Union Types - EMx 194
0:00
Playback Speed: