haskell - 如果类实例可用,则使用专门的实现

标签 haskell types typeclass

考虑以下情况:

slow_func :: Eq a  => [a] -> [a]
fast_func :: Ord a => [a] -> [a]

我有两个函数,slow_funcfast_func。这些函数是同一抽象函数的不同实现(它们执行相同的操作),但其中一个比另一个更快。仅当可以订购 a 类型时,才能实现更快的实现。有没有办法构造一个函数,在可能的情况下充当 fast_func ,否则恢复为 slow_func

as_fast_as_possible_func :: Eq a => [a] -> [a]

我已经尝试过以下操作:

{-# LANGUAGE OverlappingInstances  #-}

class Func a where
    as_fast_as_possible_func :: [a] -> [a]

instance Ord a => Func a where
    as_fast_as_possible_func = fast_func

instance Eq a => Func a where
    as_fast_as_possible_func = slow_func

不幸的是,这无法编译,生成以下错误:

Duplicate instance declarations:
  instance Ord a => Func a
    -- Defined at [...]
  instance Eq a => Func a
    -- Defined at [...]

原因是 OverlappingInstances 希望实例之一相对于实例规范而言是最专业的,而忽略其上下文(而不是使用最严格的上下文,这就是我们需要的)。

有办法做到这一点吗?

最佳答案

事实证明你确实可以。说真的,我开始认为 Haskell 中一切皆有可能...您可以使用最近宣布的结果 constraint-unions方法。我使用的代码类似于 @leftaroundabout 编写的代码。不确定我以最好的方式做到了这一点,只是尝试应用建议方法的概念:

{-# OPTIONS_GHC -Wall -Wno-name-shadowing #-}

{-# LANGUAGE AllowAmbiguousTypes        #-}
{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeOperators              #-}

module Main where

import           Data.List (group, nub, sort)

infixr 2 ||
class  c || d where
    resolve :: (c => r) -> (d => r) -> r

slowFunc :: Eq a => [a] -> [a]
slowFunc = nub

fastFunc :: Ord a => [a] -> [a]
fastFunc = map head . group . sort

as_fast_as_possible_func :: forall a. (Ord a || Eq a) => [a] -> [a]
as_fast_as_possible_func = resolve @(Ord a) @(Eq a) fastFunc slowFunc

newtype SlowWrapper = Slow Int deriving (Show, Num, Eq)
newtype FastWrapper = Fast Int deriving (Show, Num, Eq, Ord)

instance      (Ord FastWrapper || d) where resolve = \r _ -> r
instance d => (Ord SlowWrapper || d) where resolve = \_ r -> r

main :: IO ()
main = print . sum . as_fast_as_possible_func $ (Fast . round) 
                                             <$> [sin x * n | x<-[0..n]]
  where n = 20000

这里的关键部分是as_fast_as_possible_func:

as_fast_as_possible_func :: forall a. (Ord a || Eq a) => [a] -> [a]
as_fast_as_possible_func = resolve @(Ord a) @(Eq a) fastFunc slowFunc

它根据aOrd还是Eq使用适当的函数。我将 Ord 放在第一位,因为 Ord 的所有内容都会自动 Eq 并且类型检查器规则可能不会触发(尽管我没有测试过)这个函数的约束交换了)。如果您在此处使用 Slow (Fast . round) 而不是 Fast,您可以观察到明显较慢的结果:

$ time ./Nub  # With `Slow` 
Slow 166822

real    0m0.971s
user    0m0.960s
sys     0m0.008s


$ time ./Nub  # With `Fast` 
Fast 166822

real    0m0.038s
user    0m0.036s
sys     0m0.000s

更新

我已经更新了所需的实例。而不是

instance (c || Eq SlowWrapper)  where resolve = \_ r -> r

现在是

instance d => (Ord SlowWrapper || d) where resolve = \_ r -> r

Thanks @rampion for explanation!

关于haskell - 如果类实例可用,则使用专门的实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44250854/

相关文章:

haskell - 容器元素类型

haskell - 在 Haskell 中,如何将函数限制为一种数据类型的唯一构造函数?

java - 有没有办法将原始类型与其相应的包装类型进行比较?

haskell - 避免类型类中相互递归默认方法的错误

haskell - 为什么 Haskell 没有在函数签名中推断数据类型的类型类?

haskell - 为什么 sum x y 在 Haskell 中属于 (Num a) => a -> a -> a 类型?

haskell - 如何/可以将此类型制成 Monoid 实例

haskell - 仅在叶子上具有值的树

haskell - 有一个干净的导入 block 的方法? (重新导出模块?每行多个导入?)

haskell - GHC 7.6 中类型级别 Nat 的匹配