Speaker 1: You know, when you think about structural engineering, like say
Speaker 1: building a suspension bridge or I don't know, a massive skyscraper,
Speaker 1: there is this expectation of absolute mathematical precision, right.
Speaker 2: Yeah. You calculate the exact load bearing weight of the steel.
Speaker 1: Exactly, you pour the concrete bedrock to like very specific tolerances,
Speaker 1: and you know mathematically what forces that structure can actually withstand.
Speaker 2: Yeah, because the alternative is well catastrophic. I mean, it's
Speaker 2: an industry completely built on rigorous, non negotiable laws of physics.
Speaker 1: But then you look at modern web development. Yeah, and
Speaker 1: if we're being completely honest here, for those of you
Speaker 1: out there writing code every single day, a lot of
Speaker 1: the time, it feels like we are, you know, building
Speaker 1: skyscrapers out a wet cardboard and duct tape.
Speaker 2: Oh for sure, just kind of crossing her finger, yeah.
Speaker 1: Just hoping a strong wind doesn't knock the whole thing down. Yeah.
Speaker 1: I mean, we patch vulnerabilities only after they get exploited, right,
Speaker 1: We write endless tests just hoping to catch our own typos,
Speaker 1: and we just sort of accept a certain level of
Speaker 1: chaos as like the cost of doing business on the internet.
Speaker 2: It's true, I mean we've really grown accustomed to run
Speaker 2: time errors and broken states as this natural, unavoidable part
Speaker 2: of the digital ecosystem, which is kind of wild when
Speaker 2: you think about it.
Speaker 1: It really is. Well, today we're exploring a completely different paradigm.
Speaker 1: We're doing a deep dive into Michael Snoyman's book developing
Speaker 1: Web Apps with Haskell and YES'D from a Riiley Media,
Speaker 1: a classic, oh totally and frankly with its stark white
Speaker 1: cover featuring this massive, intricately detailed whinoceros beetle and all.
Speaker 2: The barcodes and QR codes on the back.
Speaker 1: Yes, the physical book itself just screams like serious, rigorous
Speaker 1: technical manual. But our mission today isn't to get bogged
Speaker 1: down in academic syntax, so don't worry about that.
Speaker 2: Oh definitely not.
Speaker 1: We are here to decode what Snyman calls safety driven
Speaker 1: web development. We're looking at how pairing the famously strict
Speaker 1: language of Askell with this web framework called YESOD creates
Speaker 1: to building incredibly fast, modular, and like virtually bug proof
Speaker 1: web application.
Speaker 2: And that's the real appeal, you know, for anyone who
Speaker 2: is just exhausted from chasing bugs. We want to understand
Speaker 2: the mechanisms, like the actual why and the how behind
Speaker 2: this architecture, because we live in an Internet that is
Speaker 2: absolutely flooded with security vulnerabilities and fragile systems, and we
Speaker 2: are looking at a framework here that's designed to structurally
Speaker 2: prevent those things from even compiling in the first place.
Speaker 1: So to understand how it actually achieves that, we have
Speaker 1: to start at the perimeter of the application. Snoyman talks
Speaker 1: about this fundamental hurdle that he calls the boundary issue.
Speaker 2: Yeah, the boundary issue.
Speaker 1: This is crucial because inside a Haskell application, everything is pristine.
Speaker 1: Haskell has a notoriously strict type system, so like the
Speaker 1: string of text is a string, and integers and integer,
Speaker 1: and the compiler physically will not allow them to mix.
Speaker 2: Right, It's a very controlled environment.
Speaker 1: But the Internet itself, it doesn't care about Haskell's rules
Speaker 1: at all, not.
Speaker 2: Even a little bit. I mean, the Internet is essentially
Speaker 2: this wild West of raw, untyped bites. Whenever a user
Speaker 2: submits a form, or you fetch data from a third
Speaker 2: party API, or even just load a record from a database,
Speaker 2: it enters your application as raw text.
Speaker 1: Just a string of characters exactly.
Speaker 2: So if a user type say twelve into an age field.
Speaker 2: The web just sends the character one and the character two.
Speaker 2: It's inherently untyped. So, however mathematically pure your application is
Speaker 2: on the inside, every single boundary with the outside world
Speaker 2: has to be meticulously sanitized. You have to translate the
Speaker 2: chaots of the Internet into the strict order of Haskell.
Speaker 1: Okay, let's unpack this. If you're building a typical node
Speaker 1: or Python backen treating that boundary is like having a
Speaker 1: nightclub bouncer who just asks, hey, are you twenty one
Speaker 1: and takes a total stranger's word for it.
Speaker 2: Yeah, that happens a lot.
Speaker 1: It implicitly trusts the data. But yes, I treats the
Speaker 1: web boundary like a bouncer who explicitly scans the ID like,
Speaker 1: checks the data types, checks the hologram, and cross references
Speaker 1: the database.
Speaker 2: Right, they aren't messing around.
Speaker 1: No, It enforces the data types at the door before
Speaker 1: anyone is allowed inside the club.
Speaker 2: That is a highly accurate analogy. And you know, this
Speaker 2: strict boundary validation is actually where the framework gets its name.
Speaker 2: The word yesod means foundation in Hebrew.
Speaker 1: Oh wow. Yeah.
Speaker 2: By explicitly declaring the exact data types you expect at
Speaker 2: the boundary. You eliminate entire classes of bugs before the
Speaker 2: code even executes.
Speaker 1: Because if the incoming data doesn't perfectly match the expected type,
Speaker 1: the foundation rejects it automatically.
Speaker 2: Right, Exactly, You don't have to write fifty unit tests
Speaker 2: to see what happens when a user submits malicious JavaScript
Speaker 2: instead of their age, because the bouncer just handles it
Speaker 2: at the door.
Speaker 1: And what's interesting is that this foundation isn't just a
Speaker 1: conceptual metaphor. I mean, every YASOD application literally revolves around
Speaker 1: a specific foundation data type that you define yourself.
Speaker 2: Right, you might just call it my app or whatever
Speaker 2: you want.
Speaker 1: Yeah, but it holds all the crucial initialized data for
Speaker 1: your entire application.
Speaker 2: It acts as the central nervous system when your application launches.
Speaker 2: This foundation data type is instantiated and it holds your
Speaker 2: database connection pools, your configuration files, your HTTP connection managers
Speaker 2: all of it.
Speaker 1: So it's all centralized exactly.
Speaker 2: By tying everything your app needs to run to the
Speaker 2: central foundation, which implements the YASAD type class, you ensure
Speaker 2: that every single request has safe, concurrent access to the
Speaker 2: exact same reliable state.
Speaker 1: Okay, so we have this uldistrict bounder at the door
Speaker 1: and a rock solid foundation inside. But you know, if
Speaker 1: you're a developer listen to this, you're probably spotting the
Speaker 1: obvious trade off here.
Speaker 2: Oh yeah, the verbosity issue, right.
Speaker 1: Because if I have to manually declare every single data type,
Speaker 1: write a parser for every incoming string, and handle every
Speaker 1: boundary explicitly, I mean I'm going to be writing thousands
Speaker 1: of lines of tedious, repetitive boilerplate code.
Speaker 2: Yes, that is the standard critique of strictly typed languages.
Speaker 2: You usually trade brevity for safety. But ysad bypasses this
Speaker 2: verbosity by leaning heavily into metaprogramming. Specifically, it uses a
Speaker 2: feature of the language called template Haskell, alongside a related
Speaker 2: tool called quasi quotes metaprogramming.
Speaker 1: So basically, code that writes code precisely.
Speaker 2: Template Haskell allows developers to write Haskell code that executes
Speaker 2: during the compilation process.
Speaker 1: During Yeah, during this code generates a Haskell abstract syntax
Speaker 1: tree or AST, which is then fed back into the compiler.
Speaker 2: Okay, let's slow down on that per second. An abstract
Speaker 2: syntax tree. For those who maybe haven't taken a compiler
Speaker 2: design course, what are we actually generating here.
Speaker 1: Think of an ast like the grammatical diagram of a sentence,
Speaker 1: but for code. When you write a line of code,
Speaker 1: the compiler has to break it down into a tree
Speaker 1: structure to understand the logic.
Speaker 2: Like this is a noun, this is a verb, exactly, but.
Speaker 1: In this case, this is a function, this is a variable,
Speaker 1: this is an operator. With template Haskell, instead of typing
Speaker 1: out repetitive text, you are giving the compiler a tiny
Speaker 1: script that programmatically generates that structural tree for you.
Speaker 2: Right, so, you might write a single line like a
Speaker 2: dollar sign, followed by a function say hamlet hamsot file
Speaker 2: myfile dot hamlet for an external file.
Speaker 1: Or use quasi quotes right yeah, which let you embed
Speaker 1: domain specific languages right inside your Haskell file using square
Speaker 1: brackets and pipes like weimleth Hello World, and behind the scenes,
Speaker 1: using a function called mech oshassade, the compiler translates that
Speaker 1: one line into all the complex parsing and routing boilerplate
Speaker 1: you normally have to type by hand.
Speaker 2: It perfectly maps the untyped web requests to your strict
Speaker 2: foundation data types.
Speaker 1: But wait, if I'm letting the framework secretly generate all
Speaker 1: my routing and parsing code behind the scenes. Don't I
Speaker 1: lose control. I mean, in my experience, magic in programming
Speaker 1: is great until it breaks, and then you have a
Speaker 1: black box you literally can't debug.
Speaker 2: What's fascinating here is that YESOD specifically avoids the black
Speaker 2: box problem. Really how it provides a flag you can
Speaker 2: pass to the compiler called dud dump splice ooh okay, yeah,
Speaker 2: if you enable this, the compiler prints out exactly the
Speaker 2: code it generated on your behalf.
Speaker 1: No way, so you can see everything everything.
Speaker 2: You can peek right under the hood and read the
Speaker 2: routing data types. The parser functions the dispatch logic. It
Speaker 2: handles the parsing, dispatching, and rendering perfectly in sync, saving
Speaker 2: human fingers from inevitable typeos. But it keeps everything entirely
Speaker 2: transparent to the developer.
Speaker 1: So it's less like magic and more like having a
Speaker 1: tireless junior developer who just writes perfect boilerplate at the
Speaker 1: speed of light and always shows their work.
Speaker 2: That's a great way to put it.
Speaker 1: Yeah, and we could actually see the power of this
Speaker 1: metaprogramming when we look at how YSAD handles routing, Because
Speaker 1: this leads us to type safe URLs, which is huge.
Speaker 2: This is arguably one of yusod's most defining features.
Speaker 1: Yeah.
Speaker 2: To appreciate it, we have to look at the routing pattern.
Speaker 2: Unlike legacy systems that map a URL directly to a
Speaker 2: physical file on a server like old PHP sites, YESOD
Speaker 2: uses a front controller pattern.
Speaker 1: Meaning every single request enters through one single point, and
Speaker 1: YESAD acts as a traffic cop basically routing it to
Speaker 1: the appropriate handler exactly.
Speaker 2: But here is the critical difference. Instead of routing based
Speaker 2: on raw strings of text, which are notoriously fragile, YESAD
Speaker 2: routes are defined as data constructors.
Speaker 1: Okay, let's define that, because data constructor is very specific
Speaker 1: to functional programming. In a traditional framework, you tell a
Speaker 1: system if the URL string is slash profile, slash home,
Speaker 1: run this function. But a data constructor is a native type, right,
Speaker 1: it's not text correct.
Speaker 2: The convention in YESOD is to add a capital R
Speaker 2: to the end for resource, so it becomes Homer Homer God.
Speaker 2: Because Homer is a data constructor, it is a plain
Speaker 2: old Haskell value. It's a structural type, just like a
Speaker 2: boolean or an integer. So the compiler inherently understands what
Speaker 2: Homer is.
Speaker 1: Here's where it gets really interesting for anyone who has
Speaker 1: ever had to refactor a massive codebase. If you define
Speaker 1: your routes as data types, you don't use string. You
Speaker 1: are ls in your HML anymore.
Speaker 2: Nope, you use the data type.
Speaker 1: Yeah. It's like using a dynamic GPS instead of drawing
Speaker 1: a static line on a paper map. If a destination moves,
Speaker 1: say you change a blog route from a numeric ID
Speaker 1: to like a date based SLUG, the GPS updates your
Speaker 1: route automatically. You don't have to go find and erase
Speaker 1: the line on.
Speaker 2: The paper map, and the maintenance implications of that are
Speaker 2: just huge. Think about a standard web application. If you
Speaker 2: change a route structure and forget to update a link
Speaker 2: somewhere on page forty seven of your site, you wouldn't
Speaker 2: know you made a mistake until the code is deployed
Speaker 2: and a user actually complains about a four h four error.
Speaker 1: Right, it's a nightmare.
Speaker 2: But in YASAUD, the Haskell compiler physically refuses to compile
Speaker 2: the application. It will throw an error and pinpoint every
Speaker 2: single line in your entire code base that's still referencing
Speaker 2: the old broken.
Speaker 1: Route, So it effectively kills dead links at compile time,
Speaker 1: they literally can't exist in production it does.
Speaker 2: It's the death of the four h four error and
Speaker 2: that metaprogramming we discussed earlier that rates a class called
Speaker 2: render root to handle the translation back to text.
Speaker 1: Right, because the browser still needs text eventually.
Speaker 2: Yeah, exactly when it compiles your type safe links into
Speaker 2: texts for the browser to read, render root generates absolute URLs,
Speaker 2: meaning it includes the full domain name.
Speaker 1: Oh interesting.
Speaker 2: Yeah, this is a subtle but vital feature because it
Speaker 2: consolidates your domain name to prevent search engines from penalizing
Speaker 2: you for duplicate content across subdomains, and it ensures formats
Speaker 2: that require absolute paths, like RSS or atom feeds, they
Speaker 2: just function flawlessly out of the box.
Speaker 1: Okay, so our inputs are meticulously sanitized by the foundation,
Speaker 1: our boilerplate is generated perfectly via the AST, and our
Speaker 1: routes are mathematically guaranteed to exist. Right. But eventually we
Speaker 1: have to actually draw the page. We have to serve HTML,
Speaker 1: CSS and JavaScript, and this requires a completely different approach
Speaker 1: to templating, doesn't it.
Speaker 2: Yes, To maintain that strict type safety all the way
Speaker 2: down to the browser, ysaud uses a suite of templating
Speaker 2: languages collectively named after Shakespeare. Love it's very clever. We
Speaker 2: have Hamlet for HTML, Lucius and Cashus for CSS, and
Speaker 2: Julius for JavaScript.
Speaker 1: The naming convention is brilliant, and just like Haskell, these
Speaker 1: languages have their own strict structural rules. Hamlet, for instance,
Speaker 1: is white space sensitive. It relies on indentation instead of
Speaker 1: closing tags. Right, so you don't write an opening tag,
Speaker 1: type your text and then pray you remember the closing
Speaker 1: tag later on. You just write the tag, indend, write
Speaker 1: your text, and the structure is heavily implied.
Speaker 2: Which naturally forces developers to write clean, visually hierarchical code.
Speaker 2: You can't be sloppy with it. But the real ingenuity
Speaker 2: of the Shakespearean templates is how they handle data interpolation
Speaker 2: and security.
Speaker 1: Yeah, let's talk about that.
Speaker 2: When you inject a variable into Hamlet, you use a
Speaker 2: specific syntax hash followed by braces like hashtag variable. And
Speaker 2: to inject one of those types safe URLs we're talking about,
Speaker 2: you use an at sign like at homer And.
Speaker 1: Because the compiler knows the exact data type you're injecting,
Speaker 1: it knows how to handle it safely. One of the
Speaker 1: most terrifying vulnerabilities in web development cross site scripting or EXSS.
Speaker 2: Yes, EXSS is a nightmare.
Speaker 1: If you aren't familiar. This is what a malicious user
Speaker 1: inputs a script into a text field like the blog comment,
Speaker 1: and if your site renders that text blindly, it executes
Speaker 1: their malicious script on your other user's machines.
Speaker 2: It's a massive vector for data theft. I mean, it's
Speaker 2: how session tokens get stolen. Most frameworks try to solve
Speaker 2: this by running text through a filter to strip out
Speaker 2: dangerous tags.
Speaker 1: Like trying to sanitize it after the fact exactly.
Speaker 2: But Hamlet prevents EXSS structurally. When you interpolate a standard
Speaker 2: string variable into a Hamlet template, it automatically entity escapes
Speaker 2: the string at the type level.
Speaker 1: Okay, meaning what exactly?
Speaker 2: It transforms characters like HTML brackets into harmless display entities,
Speaker 2: so the script cannot execute because the browser is forced
Speaker 2: to read it as literal text, not executable code.
Speaker 1: But hold on, what if I want to render preexisting HTML.
Speaker 1: Say have a developer blog and I want to display
Speaker 1: colorized code snippets that already have like SPAN tags injected
Speaker 1: into them.
Speaker 2: Right, A very common use case.
Speaker 1: Yeah, so if Hamlet escapes absolutely everything by default wanted,
Speaker 1: just mangle my snippets and print the literal words, span
Speaker 1: class equals across the screen.
Speaker 2: If we connect this to the bigger picture, this is
Speaker 2: where type level trust really outshines simple filtering. You saw.
Speaker 2: It doesn't treat everything as a dumb string. It utilizes
Speaker 2: an HTML data type, which is provided by a highly
Speaker 2: optimized package called Blaze HTML. Okay, if you want to
Speaker 2: render pre existing HTML, you don't just ask Hamlet to
Speaker 2: ignore it. You run the data through a specific function
Speaker 2: called pre escape to markup.
Speaker 1: Uh. So you are explicitly coercing the type. You are
Speaker 1: tagging it as safe HTML.
Speaker 2: Exactly, you are changing its type. When Hamlet encounters data
Speaker 2: that has been typed as HTML rather than a standard string,
Speaker 2: it recognizes that you have explicitly vouched for this data,
Speaker 2: so it passes it through unmodified.
Speaker 1: So it's not a filter, it's a type contract exactly.
Speaker 2: You have a system where the default state is absolute safety,
Speaker 2: but you retain deliberate fine grain control to overwrite it,
Speaker 2: all backed by the compiler verifying your data types.
Speaker 1: That is beautifully elegant, but it brings us to a
Speaker 1: major structural headache for modern component based design.
Speaker 2: The CSS in JavaScript routing.
Speaker 1: Yes, we have our HTML or CSS and our JavaScript
Speaker 1: safely generated by the Shakespeare languages. But HTML demands a
Speaker 1: very rigid document structure. Like CSS belongs globally up in
Speaker 1: the head tag. JavaScript usually belongs down to the bottom
Speaker 1: of the body tag. If I'm building a modular piece
Speaker 1: of a web page, say a dynamic navigation bar, how
Speaker 1: do I keep it CSS and JS organized without breaking
Speaker 1: the document structure?
Speaker 2: True modularity is notoriously difficult on the web for exactly
Speaker 2: this reason. You build a beautiful component, but to use it,
Speaker 2: you have to manually ensure its styles go to the
Speaker 2: head and its scripts go to the footer. It is
Speaker 2: but disad solves the structural disconnect with something called widgets.
Speaker 1: It's the ultimate glue for the modern web.
Speaker 2: As they say, yeah, widgets fundamentally shift how you architect
Speaker 2: a page. Instead of viewing a web page as a
Speaker 2: single monolithic tree of HTML, widgets treat the page as
Speaker 2: an assembly of distinct components. A widget is a discrete
Speaker 2: bundle that holds its own title, its own CSS declarations,
Speaker 2: its own external scripts, and its own body content.
Speaker 1: So how do you actually construct one? I mean, the
Speaker 1: documentation highlights a type class called two widget. So you
Speaker 1: take your Hamlet, your Lucius, and your Julius templates and
Speaker 1: you bundle them together using Haskell's monad do notation right,
Speaker 1: which if you aren't a Haskell developer, is essentially a
Speaker 1: way to strictly sequence operations step by step so the
Speaker 1: compiler can safely package all these different side effects like
Speaker 1: adding a style or adding a script into one cohesive
Speaker 1: monetic widget.
Speaker 2: And once you have that cohesive widget, you use a
Speaker 2: function called default layout to actually render the page.
Speaker 1: Of trying to picture how default layout works. Yeah, and
Speaker 1: it's almost like a post sorting facility.
Speaker 2: Right now, that's a good way to look at it.
Speaker 1: You hand it this bundled package, and the sorting facility unwraps.
Speaker 1: It takes the CSS and routes it directly to the
Speaker 1: head container, takes the JavaScript and routes it to the
Speaker 1: script compaigner, and takes the HTML and places it perfectly
Speaker 1: in the body. It automatically sorts everything into its legally
Speaker 1: correct place in the document.
Speaker 2: That is exactly what happens.
Speaker 1: It does the heavy lifting for you, But I have
Speaker 1: to push back here for a second. If I'm reusing
Speaker 1: these widgets, Let's say I dropped three different dynamic image
Speaker 1: carousels onto the exact same page. Don't I run the
Speaker 1: risk of CSS class name collisions? Oh? Sure, because if
Speaker 1: the framework is auto sorting my CSS into the global headtag,
Speaker 1: I feel like that's just a recipe for styling nightmares
Speaker 1: and dombloat.
Speaker 2: Well it would be if you were hard coding the
Speaker 2: class names.
Speaker 1: Ah.
Speaker 2: Okay, Yes, it anticipates this with a function called new
Speaker 2: ident new dent. Yeah. When you build a widget, you
Speaker 2: can use new ident to automatically generate mathematically unique IDs
Speaker 2: and class names for that specific instance of the widget.
Speaker 2: Oh wow, it scopes the CSS locally. You can drop
Speaker 2: that image carousel onto the page twenty times, and new
Speaker 2: iden guarantees there will be zero styling collisions.
Speaker 1: So what does this all mean? The modularity is basically absolute.
Speaker 1: You write the complex logic once and you can drop
Speaker 1: it anywhere, knowing it will sort its own dependencies and
Speaker 1: never bleed into the rest of the page. And from
Speaker 1: a developer experience standpoint, you really get the best of
Speaker 1: both worlds. For local development, YASAD provides this tool yesad Devil,
Speaker 1: which auto rebuilds your code on the fly, so you
Speaker 1: get the rapid iterative prototyping speed of a dynamic scripting language.
Speaker 2: It feels very fast to work with, but then when
Speaker 2: you compile it for production, you deploy it on ysad's
Speaker 2: custom web server called WARP, and because it's compiled down
Speaker 2: to machine code, the memory footprint is incredibly small and
Speaker 2: the raw performance is frankly staggering compared to traditional interpreted frameworks.
Speaker 1: So to pull all of this together for everyone listening,
Speaker 1: we looked at how Haskell's strict typing acts as a
Speaker 1: boundary be right, how the foundation provides a safe central state,
Speaker 1: how metaprogramming and type safe URLs just eradicate deadlinks, how
Speaker 1: Shakespearean templates structurally defeat XSS, and how modular widgets cleanly
Speaker 1: assemble the final product.
Speaker 2: It really is a comprehensive architecture for building bulletproof web applications.
Speaker 2: It redefines the baseline of what we should expect from
Speaker 2: our tools.
Speaker 1: Yeah, it does, And you know.
Speaker 2: This raises an important question something for you to think
Speaker 2: about after we sign off.
Speaker 1: Cable's here.
Speaker 2: If you saw it uses compile time metaprogramming to automatically
Speaker 2: handle routing logic, enforce data sanitization at the boundaries, and
Speaker 2: structurally prevent EXSS attacks before the serer even starts. Yeah, well,
Speaker 2: could this paradigm eventually make manual unit testing obsolete for
Speaker 2: UI and routing?
Speaker 1: Oh wow?
Speaker 2: I mean if the compiler mathematically guarantees the code is
Speaker 2: structurally perfect, do we still need armies of QA testers
Speaker 2: writing brittle browser automation scripts or does the compiler itself
Speaker 2: evolve into the ultimate QA department.
Speaker 1: That is a wild thought to leave on a world
Speaker 1: where the compiler actually does the QA for you. Well,
Speaker 1: that is all the time we have for today's deep dive.
Speaker 1: Thank you for joining us as we explore the underlying
Speaker 1: mechanisms of safety driven web development. Remember, the Internet might
Speaker 1: be made of wet cardboard and duct tape, but your
Speaker 1: applications foundation definitely doesn't have to be. Keep asking questions,
Speaker 1: keep exploring, and keep digging deep. We'll catch you next time.