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个:
- case分支归属错误:嵌套case时括号/分支层级写错,处理
Discard操作的分支被错误归到内层匹配lcards(抽牌堆)的case下,导致编译器认为你在给卡牌列表匹配move类型的分支,触发类型不匹配。 - 抽牌逻辑参数错误:
sum_cards要求传入卡牌列表,代码中错误传入单张卡牌c;- 抽牌后应计算加入新卡后的持有卡牌总点数判断是否超限,而非单张卡点数;
- 计算得分时错误传入抽牌堆
lcards,score函数需要接收当前持有卡牌列表作为参数。
- 弃牌逻辑参数错误:弃牌不修改抽牌堆,代码递归调用时错误传入初始完整卡牌堆
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
相关产品推荐
相关产品推荐

