Haskell:无法将预期类型“a -> a”与实际类型“Failable (a -> a)”相匹配

Haskell : Couldn't match expected type ‘a -> a’ with actual type ‘Failable (a -> a)’

我正尝试在 Haskell 中尝试仿函数和安全除法,但我遇到了这个小错误,而且我无法弄清楚原因。

这是我的代码:

module Main
where

data Failable a = Failure String | Success a

instance (Show a) => Show (Failable a) where
    show (Failure s) = "Failure : " ++ s
    show (Success x) = "Success : " ++ (show x)

instance Functor Failable where
    fmap f (Success x) = Success (f x)
    fmap _ (Failure s) = Failure s

(//) :: (Num a) => Failable a -> Failable a -> Failable a
_ // (Success 0) = Failure "Division by zero"
x // y = fmap (fmap (/) x) y

main = do
    print $ (Success 1) // (Success 2)

并且输出:

main.hs:16:16:
Couldn't match expected type ‘a -> a’
            with actual type ‘Failable (a -> a)’
Relevant bindings include
  y :: Failable a (bound at main.hs:16:6)
  x :: Failable a (bound at main.hs:16:1)
  (//) :: Failable a -> Failable a -> Failable a
    (bound at main.hs:15:1)
Possible cause: ‘fmap’ is applied to too many arguments
In the first argument of ‘fmap’, namely ‘(fmap (/) x)’
In the expression: fmap (fmap (/) x) y

我想出了这个定义:

(//) :: (Eq a, Fractional a) => Failable a -> Failable a -> Failable a
_ // (Success 0) = Failure "Division by zero"
f@(Failure _) // _ = f
_ // f@(Failure _) = f
(Success v) // y = fmap (/v) y

你的定义发生了什么是你在 (//) 的第二个等式中传递给 fmapFailable (a -> a) 类型的东西,它期望有 Failable (a -> a) 类型的东西=18=].

你可以做的是使它成为Applicative的一个实例,并使你的Functor成为可应用的,如下:

class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b  

并同样创建一个仿函数的实例:

instance Applicative Failable where
  pure = Success
  (<*>) fun (Success x) = fmap (\f -> f x) fun

因此,您可以定义(//)如下:

(//) :: (Eq a, Fractional a) => Failable a -> Failable a -> Failable a
_ // (Success 0) = Failure "Division by zero"
x // y = (<*>) (fmap (/) x) y

更新:

正如 Asad 在评论中指出的那样,写最后一行的更自然、更清晰的方式是:

x // y = (/) <$> x <*> y

我认为您正试图将 (/) 提升为 Failable 函子。 您可以使用模式匹配来实现,但我认为您希望单独使用 Functor 函数 (fmap)。

然而这是不可能的。充其量,fmap (/)可以提供

(/)      :: Num a => a          ->           a -> a
fmap (/) :: Num a => Failable a -> Failable (a -> a)

而且我们没有通用的方法将丑陋的 Failable (a -> a) 变成 Failable a -> Failable a

这就是为什么我们有更强大的类型 class:Applicative,正是为了做到这一点

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

所以...恭喜。您刚刚发现了引入这种类型 class 的问题。

我建议您编写一个 Applicative 实例。之后,(省略零检查)

(//) = liftA2 (/)
-- or, (this being the most popular style, nowadays)
x // y = (/) <$> x <*> y
-- or,
x // y = fmap (/) x <*> y