codehakase.com

Posts tagged "haskell"

GADTs for Type-Level Domain Logic

May 7, 2025

I’ve been leveraging Generalized Algebraic Data Types (GADTs) in Haskell and found them exceptionally powerful for encoding domain-specific rules and state transitions directly into the type system. This is particularly useful for defining operations that are based on explicitly tagged types or state, and behave differently.

Consider a scenario with distinct processing steps, each associated with specific data:

{-# LANGUAGE GADTs #-}

module StepProcessor where

data AuthData = AuthData 
    { userId :: String
    , token :: String 
    } 
    deriving (Show)

data ReviewData = ReviewData 
    { documentId :: Int
    , comments :: String
    }
    deriving (Show)

data Step a where
  AuthStep   :: AuthData   -> Step AuthData
  ReviewStep :: ReviewData -> Step ReviewData

validateAuth :: AuthData -> IO ()
validateAuth ad = putStrLn $ "Validating auth for user: " ++ userId ad

runReview :: ReviewData -> IO ()
runReview rd = putStrLn $ "Running review for doc: " ++ show (documentId rd) ++ " with comments: " ++ comments rd

processStep :: Step a -> IO ()
processStep (AuthStep   payload) = validateAuth payload
processStep (ReviewStep payload) = runReview    payload

-- Example Usage:
-- let authAction = AuthStep (AuthData "user123" "secret_token")
-- let reviewAction = ReviewStep (ReviewData 456 "Looks good!")
--
-- processStep authAction 
-- processStep reviewAction
--
-- The following would be a compile-time error:
-- processStep (AuthStep (ReviewData 789 "Wrong data!")) -- Type mismatch!

Composable Parsers with Attoparsec

May 6, 2025

I’ve been diving into Haskell’s attoparsec and I’m impressed by how it simplifies building complex parsers. Here’s a quick example for a DSL snippet like "name=Some Name;age=42;", using Data.Attoparsec.Text:

import Control.Applicative ((<*), (*>))
import Data.Attoparsec.Text
import Data.Text (Text)
import GHC.Generics (Generic)

data Person = Person { name :: Text, age  :: Int }
  deriving (Show, Eq, Generic)

personParser :: Parser Person
personParser = Person
    <$> (string "name=" *> takeTill (== ';') <* char ';')
    <*> (string "age="  *> decimal        <* char ';')

This parser defines a Person and then, using applicative style, consumes "name=", takes characters until a semicolon (discarding the semicolon), then consumes "age=", parses a decimal (discarding its trailing semicolon), and populates the Person fields.