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

SML编写officiate函数类型不匹配错误排查

SML纸牌游戏officiate函数类型错误修复

我正在尝试使用SML编写满足如下需求的纸牌游戏程序:

游戏基于卡牌列表(card-list)和目标值(goal)开展,玩家初始持有卡牌列表(held-cards)为空。玩家每回合可执行两种操作:抽牌(Draw)指将卡牌列表的首张卡牌移出卡牌列表,加入持有卡牌列表;弃牌(Discard)指选择一张自身持有的卡牌移除。游戏在玩家选择不再操作、或持有卡牌的点数总和大于目标值时结束。

请编写函数officiate用于“运行游戏”,该函数接收三个参数:卡牌列表、操作列表(记录玩家每一步的操作)、整型目标值,按顺序处理操作列表中全部或部分操作后,返回游戏结束时的得分。要求使用局部定义的递归辅助函数,通过多个参数共同表示游戏当前状态,具体规则如下:

  • 游戏初始状态下持有卡牌为空列表。
  • 无剩余操作时游戏结束(操作列表为空代表玩家选择停止游戏)。
  • 若玩家弃置某张卡牌c,持有卡牌列表移除c、卡牌列表保持不变,继续递归运行游戏;若c不在持有卡牌列表中,则抛出IllegalMove异常。
  • 若玩家执行抽牌操作时卡牌列表已为空,游戏直接结束;若抽牌后持有卡牌点数总和超过目标值,抽牌完成后游戏结束;否则持有卡牌列表加入该卡牌、卡牌列表移除首张卡牌后继续游戏。

原有实现代码

datatype suit = Clubs | Diamonds | Hearts | Spades
datatype rank = Jack | Queen | King | Ace | Num of int 
type card = suit * rank

datatype move = Discard of card | Draw 

exception IllegalMove

fun remove_card (cs, c, e) =
    let
       fun delete_it (cs, c, e, count, acs) =
           case cs of
           [] => if count = 0 then raise e else acs
         | cp::cs' => if cp = c andalso count = 0
                      then delete_it (cs', c, e, count + 1, acs)
                      else
                          if cp = c then delete_it (cs', c, e, count + 1, [cp] @ acs) else delete_it (cs', c, e, count, [cp] @ acs)                
    in
       delete_it (cs, c, e, 0, [])
    end 

fun sum_cards (loc) =
    let
        fun sum_it (loc, sum) =
           case loc of
           [] => sum
         | c::loc' => sum_it (loc', sum + card_value (c))
    in
        sum_it (loc, 0)
    end 

fun score (loc, goal) =
    let
        val sum = sum_cards (loc)
        val all_same = all_same_color (loc)         
    in
        let
           fun prem_score (sum, goal) =
               if sum > goal
               then 3 * (sum - goal)
               else goal - sum     
        in
           let 
              val pre_score = prem_score (sum, goal)
              fun final_score (all, pre) =
                  if all
                  then pre div 2
                  else pre
        in
           final_score (all_same, pre_score)
        end         
    end
    end

fun officiate (loc, lom, goal) =
    let
    fun get_score (lcards, lohc, lmoves, goal, e) =
        case lmoves of
        [] => score (lcards, goal)
          | Draw::lmoves' => case lcards of
                   [] => score (lcards, goal)
                 | c::loc' => if sum_cards (c) > goal
                          then score (lcards, goal)
                          else get_score (loc', c::lohc, lmoves', goal, e)
          | d::lmoves' => case d of
                 Discard c => get_score (loc, remove_card (lohc, c, e), lmoves', goal, e)        
    in
    get_score (loc, [], lom, goal, IllegalMove)
    end                    

代码中sum_cards、score、remove_card三个函数经单独测试可正常运行,但officiate函数编译时抛出如下类型不匹配错误:

hw2.sml:196.17-196.60 Error: operator and operand don't agree [tycon mismatch]
  operator domain: (suit * rank) list * (suit * rank) list list * 'Z * int * 
                   'Y
  operand:         (suit * rank) list list * (suit * rank) list list * 
                   move list * int * 'Y
  in expression:
    get_score (loc',c :: lohc,lmoves',goal,e)
hw2.sml:198.29-198.78 Error: operator and operand don't agree [tycon mismatch]
  operator domain: (suit * rank) list list * (suit * rank) list * exn
  operand:         (suit * rank) list list * card * 'Z
  in expression:
    remove_card (lohc,c,e)
hw2.sml:192.27-198.78 Error: types of rules don't agree [tycon mismatch]
  earlier rule(s): (suit * rank) list list -> int
  this rule: move list -> 'Z
  in rule:
    :: (d,lmoves') =>
      (case d
        of Discard c => get_score (<exp>,<exp>,<exp>,<exp>,<exp>))
hw2.sml:192.27-198.78 Error: case object and rules don't agree [tycon mismatch]
  rule domain: (suit * rank) list list
  object: (suit * rank) list
  in expression:
    (case lcards
      of nil => score (lcards,goal)
       | :: (c,loc') =>
           if sum_cards <exp> > goal
           then score (<exp>,<exp>)
           else get_score (<exp>,<exp>,<exp>,<exp>,<exp>)
       | :: (d,lmoves') =>
           (case d
             of Discard <pat> => get_score <exp>))
val it = () : unit

问题定位

错误并非多层case嵌套本身导致,核心问题有3个:

  1. case分支归属错误:嵌套case时括号/分支层级写错,处理Discard操作的分支被错误归到内层匹配lcards(抽牌堆)的case下,导致编译器认为你在给卡牌列表匹配move类型的分支,触发类型不匹配。
  2. 抽牌逻辑参数错误:
    • sum_cards要求传入卡牌列表,代码中错误传入单张卡牌c;
    • 抽牌后应计算加入新卡后的持有卡牌总点数判断是否超限,而非单张卡点数;
    • 计算得分时错误传入抽牌堆lcards,score函数需要接收当前持有卡牌列表作为参数。
  3. 弃牌逻辑参数错误:弃牌不修改抽牌堆,代码递归调用时错误传入初始完整卡牌堆loc,会导致之前已抽出的卡牌被重置回抽牌堆,逻辑完全错误。

修复方案

重构辅助函数的模式匹配逻辑,直接在函数参数上拆分不同状态,避免嵌套case的分支归属错误,同时修正所有参数传递问题:

fun officiate (card_list, move_list, goal) =
    let
        (* 辅助函数参数:剩余抽牌堆、当前持有卡牌、剩余操作列表、异常 *)
        fun run_game (_, held, [], _) = score(held, goal)
          | run_game ([], held, _, _) = score(held, goal)
          | run_game (top::rest_cards, held, Draw::rest_moves, e) =
            let
                val new_held = top :: held
                val total = sum_cards(new_held)
            in
                if total > goal
                then score(new_held, goal)
                else run_game(rest_cards, new_held, rest_moves, e)
            end
          | run_game (cards, held, (Discard c)::rest_moves, e) =
            run_game(cards, remove_card(held, c, e), rest_moves, e)
    in
        run_game(card_list, [], move_list, IllegalMove)
    end

修复点说明:

  • 无剩余操作、抽牌堆为空时直接用当前持有卡牌计算得分结束;
  • 抽牌时先构造新的持有卡牌列表,计算总点数,超限则直接用新列表算分结束,否则更新抽牌堆和持有列表继续递归;
  • 弃牌时保持抽牌堆参数不变,仅更新持有卡牌列表,不会重置抽牌状态;
  • 所有score调用均传入当前持有卡牌列表,符合函数参数要求。

内容的提问来源于stack exchange,提问作者Zakaria Bennane

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 06:18:22