You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell中如何枚举求和类型所有值以生成HTML表单

从Haskell求和类型生成动态HTML表单的实现方案

1. 为类型派生必要的类型类

首先要让枚举类型支持遍历所有值,同时方便后续的字符串转换与解析,修改类型定义并派生核心类型类:

data Color = Green | Yellow | Red deriving (Eq, Show, Enum, Bounded)
data Fruit = Apple Color Int | Banana Color Int String deriving (Eq, Show)
  • Enum + Bounded:让Color这种纯枚举类型可以直接遍历所有值
  • Show:用于将类型值转为字符串,作为下拉选项的value属性
  • Eq:方便后续的相等性判断

2. 获取类型的可选项集合

处理纯枚举类型(Color)

对于Color这种无参数的求和类型,直接通过[minBound..maxBound]就能拿到所有枚举值:

allColors :: [Color]
allColors = [minBound..maxBound]

处理带参数的求和类型(Fruit)

Fruit的构造器带参数,无法生成所有实例(参数是无限的),我们需要的是构造器选项(Apple、Banana),因此自定义一个枚举类型来表示这些选项:

data FruitConstructor = AppleConstructor | BananaConstructor deriving (Eq, Show, Enum, Bounded)

allFruitConstructors :: [FruitConstructor]
allFruitConstructors = [minBound..maxBound]

3. 渲染HTML下拉列表

以blaze-html库为例,编写通用下拉列表渲染函数,同时支持自定义显示名称:

import Text.Blaze.Html5 as H
import Text.Blaze.Html5.Attributes as A

-- 自定义类型类,控制下拉选项的显示文本
class Display a where
  displayName :: a -> String

instance Display Color where
  displayName Green = "Green"
  displayName Yellow = "Yellow"
  displayName Red = "Red"

instance Display FruitConstructor where
  displayName AppleConstructor = "Apple"
  displayName BananaConstructor = "Banana"

-- 通用下拉渲染函数
renderDropdown :: (Display a, Show a) => String -> [a] -> Html
renderDropdown fieldName options = H.select ! A.name fieldName $ do
  mapM_ (\opt -> 
    H.option ! A.value (stringValue $ show opt) $ toHtml $ displayName opt
  ) options

-- 生成Color和Fruit构造器的下拉列表
colorDropdown :: Html
colorDropdown = renderDropdown "color" allColors

fruitTypeDropdown :: Html
fruitTypeDropdown = renderDropdown "fruit-type" allFruitConstructors

4. 动态显示对应输入框(前端逻辑)

通过JavaScript监听下拉选择事件,动态显示/隐藏对应输入框(比如选中Banana时显示String输入框):

<form>
  <div>
    <label>Fruit Type:</label>
    <!-- 插入Haskell生成的fruitTypeDropdown -->
    <select name="fruit-type" id="fruit-type">
      <option value="AppleConstructor">Apple</option>
      <option value="BananaConstructor">Banana</option>
    </select>
  </div>
  <div>
    <label>Color:</label>
    <!-- 插入Haskell生成的colorDropdown -->
    <select name="color">
      <option value="Green">Green</option>
      <option value="Yellow">Yellow</option>
      <option value="Red">Red</option>
    </select>
  </div>
  <div>
    <label>Quantity:</label>
    <input type="number" name="quantity" required>
  </div>
  <div id="variety-input" style="display: none;">
    <label>Banana Variety:</label>
    <input type="text" name="variety">
  </div>
  <button type="submit">Submit</button>
</form>

<script>
  const fruitSelect = document.getElementById('fruit-type');
  const varietyInput = document.getElementById('variety-input');

  fruitSelect.addEventListener('change', () => {
    varietyInput.style.display = fruitSelect.value === 'BananaConstructor' ? 'block' : 'none';
  });
</script>

5. 后端解析表单数据

使用readMaybe解析提交的字符串,构造对应的Fruit实例:

import Text.Read (readMaybe)
import Control.Monad (guard)

parseFruit :: String -> String -> String -> Maybe String -> Maybe Fruit
parseFruit fruitTypeStr colorStr quantityStr varietyStr = do
  fruitType <- readMaybe fruitTypeStr
  color <- readMaybe colorStr
  quantity <- readMaybe quantityStr
  case fruitType of
    AppleConstructor -> Just $ Apple color quantity
    BananaConstructor -> do
      variety <- varietyStr
      Just $ Banana color quantity variety

内容的提问来源于stack exchange,提问作者Shawn Zhang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 23:50:17