Haskell中类型类中的异常约束

Haskell中类型类中的异常约束,haskell,exception,io,filesystems,Haskell,Exception,Io,Filesystems,我有个问题。我正在尝试写一个文件系统管理的类类型。我从一个简单的类开始: import Control.Exception class (Monad m) => Commands m where --Similar to catch :: Exception e => IO a -> (e -> IO a) -> IO a fsCatch :: Exception e => m a -> (e -> m a)

我有个问题。我正在尝试写一个文件系统管理的类类型。我从一个简单的类开始:

import Control.Exception

class (Monad m) => Commands m where

  --Similar to catch :: Exception e => IO a -> (e -> IO a) -> IO a
  fsCatch
    :: Exception e
    => m a
    -> (e -> m a)
    -> m a

  setCurDir
    :: FilePath
    -> m ()

  caughtError
    :: Exception e
    => e
    -> m ()
  cd
    :: FilePath
    -> m ()
  cd path = fsCatch (setCurDir path) caughtError
有一个函数cd可以在目录上移动。它尝试使用setCurDir更改目录,但需要实现,如果出现问题,它将调用caughtError函数

我想创建两个实例:用于IO和我的“玩具”文件系统数据类

对于IO来说,它看起来像这样:

instance Commands IO where
  fsCatch = catch

  setCurDir = setCurrentDirectory

  caughtError ex | isDoesNotExistError ex = putStrLn "No such directory"
                 | etc...
IO有自己的IOError。我将为我的玩具文件系统生成MyFSError。但是我在我的type类中遇到了这个错误:

 * Could not deduce (Exception e0) arising from a use of `fsCatch'
      from the context: Commands m
        bound by the class declaration for `Commands'
        at D:\\University\FP\hw3-olegggatttor\hw3\src\Commands.hs:6:20-27
      The type variable `e0' is ambiguous
      These potential instances exist:
        instance Exception ErrorCall -- Defined in `GHC.Exception'
        instance Exception ArithException
          -- Defined in `GHC.Exception.Type'
        instance Exception SomeException -- Defined in `GHC.Exception.Type'
        ...plus 10 others
        ...plus three instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    * In the expression: fsCatch (setCurDir path) catchedError
      In an equation for `cd':
          cd path = fsCatch (setCurDir path) catchedError
   |
61 |   cd path = fsCatch (setCurDir path) catchedError
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.
问题在哪里?如何解决?必须使用类类型。

问题是cd需要异常e约束来调用fsCatch和caughtError。然而,cd的定义或它的类型签名中没有提供这样的约束。见鬼,cd的类型签名中甚至没有提到类型e

对于这个特定的类型类,看起来您希望monad m确定异常类型e。如果m~IO,则e~IOError;如果m~ToyFilesystem,则e~MyFSError。这可以使用函数依赖项或关联的类型族来实现。我认为大家的共识可能是,关联类型族方法是更现代、更直接的方法,因此应该是首选方法,但我将向大家展示这两种方法

对于函数依赖项实现,您可以重新参数化类型类以包含异常类型e,并添加注释m->e,该注释指示monad的类型唯一地确定异常的类型:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}

class (Monad m, Exception e) => Commands m e | m -> e where
  fsCatch :: m a -> (e -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: e -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError
IO实例如下所示:

instance Commands IO IOError where
  fsCatch = catch
  setCurDir = setCurrentDirectory
  caughtError ex | isDoesNotExistError ex = putStrLn "No such directory"
请注意,在这个类中实际上根本不需要Monad m或Exception e约束,因此将类声明切换为:

class Commands m e | m -> e where
  ...
很好。我认为这是一个更好的实践——在类声明中不应该出现Monad或异常约束——但这是另一个SO问题的答案

对于关联的类型族实现,可以向类添加类型族声明,该声明将monad显式映射到其异常类型:

class (Monad m, Exception (E m)) => Commands m where
  type E m  -- exception type for monad m
  fsCatch :: m a -> (E m -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: E m -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError
同样,这里不需要约束,您可以编写:

class Commands m where
  ...
实例然后为monad m指定类型em:

功能依赖关系的完整示例:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}

import Control.Exception
import System.Directory
import System.IO.Error

class Commands m e | m -> e where
  fsCatch :: m a -> (e -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: e -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError

instance Commands IO IOError where
  fsCatch = catch
  setCurDir = setCurrentDirectory
  caughtError ex | isDoesNotExistError ex = putStrLn "No such directory"
对于关联的类型族:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}

import Control.Exception
import System.Directory
import System.IO.Error

class Commands m where
  type E m
  fsCatch :: m a -> (E m -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: E m -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError

instance Commands IO where
  type E IO = IOError
  fsCatch = catch
  setCurDir = setCurrentDirectory
  caughtError ex | isDoesNotExistError ex = putStrLn "No such directory"
问题是cd需要一个异常e约束来调用fsCatch和caughtError。然而,cd的定义或它的类型签名中没有提供这样的约束。见鬼,cd的类型签名中甚至没有提到类型e

对于这个特定的类型类,看起来您希望monad m确定异常类型e。如果m~IO,则e~IOError;如果m~ToyFilesystem,则e~MyFSError。这可以使用函数依赖项或关联的类型族来实现。我认为大家的共识可能是,关联类型族方法是更现代、更直接的方法,因此应该是首选方法,但我将向大家展示这两种方法

对于函数依赖项实现,您可以重新参数化类型类以包含异常类型e,并添加注释m->e,该注释指示monad的类型唯一地确定异常的类型:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}

class (Monad m, Exception e) => Commands m e | m -> e where
  fsCatch :: m a -> (e -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: e -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError
IO实例如下所示:

instance Commands IO IOError where
  fsCatch = catch
  setCurDir = setCurrentDirectory
  caughtError ex | isDoesNotExistError ex = putStrLn "No such directory"
请注意,在这个类中实际上根本不需要Monad m或Exception e约束,因此将类声明切换为:

class Commands m e | m -> e where
  ...
很好。我认为这是一个更好的实践——在类声明中不应该出现Monad或异常约束——但这是另一个SO问题的答案

对于关联的类型族实现,可以向类添加类型族声明,该声明将monad显式映射到其异常类型:

class (Monad m, Exception (E m)) => Commands m where
  type E m  -- exception type for monad m
  fsCatch :: m a -> (E m -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: E m -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError
同样,这里不需要约束,您可以编写:

class Commands m where
  ...
实例然后为monad m指定类型em:

功能依赖关系的完整示例:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}

import Control.Exception
import System.Directory
import System.IO.Error

class Commands m e | m -> e where
  fsCatch :: m a -> (e -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: e -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError

instance Commands IO IOError where
  fsCatch = catch
  setCurDir = setCurrentDirectory
  caughtError ex | isDoesNotExistError ex = putStrLn "No such directory"
对于关联的类型族:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}

import Control.Exception
import System.Directory
import System.IO.Error

class Commands m where
  type E m
  fsCatch :: m a -> (E m -> m a) -> m a
  setCurDir :: FilePath -> m ()
  caughtError :: E m -> m ()
  cd :: FilePath -> m ()
  cd path = fsCatch (setCurDir path) caughtError

instance Commands IO where
  type E IO = IOError
  fsCatch = catch
  setCurDir = setCurrentDirectory
  caughtError ex | isDoesNotExistError ex = putStrLn "No such directory"

非常感谢你!函数依赖性看起来是解决我的问题的完美方案!很好的回答非常感谢!函数依赖性看起来是解决我的问题的完美方案!很好的回答