在 Haskell 中编写 AI Solver 时的类型变量不明确

Posted

技术标签:

【中文标题】在 Haskell 中编写 AI Solver 时的类型变量不明确【英文标题】:Ambiguous type variable when programming an AI Solver in Haskell 【发布时间】:2013-02-13 10:12:15 【问题描述】:

我正在 Haskell 中为 Coursera 的 AI 规划课程编写一个 AI General Problem Solver,而 ghci 抱怨一个模棱两可的类型变量。这是 Haskell 代码和我得到的错误:

-- Solver.hs
-# LANGUAGE GADTs,FlexibleInstances,UndecidableInstances,ScopedTypeVariables,TypeFamilies,MultiParamTypeClasses #-

module Solver
(Solver,State,Transition)
where

class (Show t,Eq t) => Transition t where
 transition :: State s => s -> t -> s

class (Show s,Eq s) => State s where
 getPossibleTransitions :: Transition t => s -> [t]
 isStateValid :: s -> Bool
 isGoalState :: s -> Bool

class Solver s t where
 getPossibleNextStates :: s -> [s]
 isStateVisited :: [s] -> s -> Bool
 getNextFringeStates :: [s] -> [[s]]
 --getNextGeneration :: [s] -> [s] -> [s]

flatten :: [[a]] -> [a]
flatten [] = []
flatten listOfLists = (head listOfLists) ++ (flatten (tail listOfLists))

instance (State s,Transition t) => Solver s t where

 getPossibleNextStates (state::s) =
  filter isStateValid (map transitionFunction possibleTransitions)
  where
   transitionFunction = (transition state)::(t -> s)
   possibleTransitions = (getPossibleTransitions state)::([t])

 isStateVisited visitedStates state =
  any (== state) visitedStates

 getNextFringeStates (states::[s]) =
  map (getPossibleNextStates :: (s -> [s])) (states::[s])

-- COMPILATION:
-
Prelude> :l Solver.hs
[1 of 1] Compiling Solver           ( Solver.hs, interpreted )

Solver.hs:38:8:
    Ambiguous type variable `t0' in the constraint:
      (Transition t0) arising from a use of `getPossibleNextStates'
    Probable fix: add a type signature that fixes these type variable(s)
    In the first argument of `map', namely
      `(getPossibleNextStates :: s -> [s])'
    In the expression:
      map (getPossibleNextStates :: s -> [s]) (states :: [s])
    In an equation for `getNextFringeStates':
        getNextFringeStates (states :: [s])
          = map (getPossibleNextStates :: s -> [s]) (states :: [s])
Failed, modules loaded: none.
-

【问题讨论】:

我也想不通。 (但顺便说一句,您的 flatten 函数实际上等同于 Prelude 中的 concat 你有一个类型变量 t 用于类求解器,但它在任何地方都没有使用。也许你可以删除它,看看会发生什么。顺便说一句,你真的需要 ScopedTypeVariables 的东西吗? 【参考方案1】:

我认为您有一个 type-class-itis 的实例。也就是说,太多的类型类并没有真正完成任何事情,导致代码复杂而难以推理。

有助于诊断的 type-class-itis 的一个症状是需要不断引入新的语言功能以使工作正常进行。如果您继续沿着这条路线走,那么稍后您会发现自己需要编写许多实际上不包含任何数据的“虚拟类型”,并且仅存在以便您可以将它们变成各种类型类的实例。

您可以在 Luke Palmer 和 Gabriel Gonzalez 的这些博客文章中阅读有关 type-class-itis 的更多信息(LP 更温和,GG 更...极端)

Haskell Antipattern: Existential Typeclass Scrap Your Type Classes

更好的解决方案是记住函数也是数据。您可以将所需的功能包装到记录中,然后将记录传递给周围。例如,在你的情况下,我可能会这样构建它:

module Solver where

data State s t = State  state :: s
                       , getPossibleTransitions :: [t]
                       , isValid :: Bool
                       , isGoal :: Bool
                       , transition :: t -> State s t 

getPossibleNextStates :: State s t -> [State s t]
getPossibleNextStates s = filter isValid (map transitionFunction possibleTransitions)
    where
        transitionFunction  = transition s
        possibleTransitions = getPossibleTransitions s

isStateVisited :: Eq s => [s] -> State s t -> Bool
isStateVisited visitedStates s = any (== state s) visitedStates

getNextFringeStates :: [State s t] -> [[State s t]]
getNextFringeStates states = map getPossibleNextStates states

请注意,我不需要引入任何特殊的语言功能,而且代码也短得多 - 19 行而不是 38 行,尽管我包含了所有类型签名。

祝你好运!

【讨论】:

好答案。我经常看到代码试图将 haskell 强制转换为程序员所熟悉的某种面向对象的命令方式。唯一的影响是不必要的复杂代码和特定扩展的滥用。 我继续在我的博客文章中添加了免责声明,以便人们知道我不再那么激进地反对使用类型类。 @GabrielGonzalez 当我说“极端”时,我的意思并不是批评。我一直认为你在那篇文章中并不是 100% 认真的——你尽可能地推动它以表明一个观点! 别担心。没有冒犯!一段时间以来,我一直想添加该免责声明,但一直忘记它。 我也喜欢这个解决方案。它更简洁,更简洁。谢谢你。 :)【参考方案2】:

Eric Kow 通过使用函数依赖解决了我的问题。他继续按照我的要求使用类型类。这是他的解决方案,编译起来就像一个魅力:

http://pastebin.com/tnqW2QGn

这是我们找到解决方案的 Haskell facebook 群组:

https://www.facebook.com/groups/programming.haskell/

-- Solver.hs
-# LANGUAGE FunctionalDependencies #-
-# LANGUAGE MultiParamTypeClasses  #-
-# LANGUAGE ScopedTypeVariables    #-

module Solver
    (Solver,State,Transition)
  where

class (Show t,Eq t) => Transition t where
    transition :: State s => s -> t -> s

class (Show s,Eq s) => State s where
    getPossibleTransitions :: Transition t => s -> [t]
    isStateValid :: s -> Bool
    isGoalState  :: s -> Bool

class (State s, Transition t) => Solver s t | s -> t where

    getPossibleNextStates :: s -> [s]
    getPossibleNextStates state =
       filter isStateValid (map transitionFunction possibleTransitions)
      where
       transitionFunction  = transition state :: t -> s
       possibleTransitions = getPossibleTransitions state

    isStateVisited        :: [s] -> s -> Bool
    isStateVisited visitedStates state =
       any (== state) visitedStates

    getNextFringeStates :: [s] -> [[s]]
    getNextFringeStates = map getPossibleNextStates

【讨论】:

“有助于诊断的 type-class-itis 的一个症状是需要不断引入新的语言功能以使工作正常运行。” @ChrisTaylor 是的,但他真正需要的两个是相当常见的。顺便说一句,当我们需要 ScopedTypeVariables 之类的东西时,GHC 会警告我们,但以后不要使用它吗? @Ingo 默认不显示警告。可以选择这样做。

以上是关于在 Haskell 中编写 AI Solver 时的类型变量不明确的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Haskell 中提供屏幕图像作为程序的输入

如何用 Haskell 编写 Windows 服务应用程序?

Haskell 生命游戏在启动时崩溃

如何在 Haskell 中编写游戏循环?

Haskell 类型安全的空间使用

Haskell:当不需要日志时,让 Writer 和普通代码一样高效