考虑以下情况:
slow_func :: Eq a => [a] -> [a]
fast_func :: Ord a => [a] -> [a]
我有两个功能,slow_func
和fast_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
希望其中一个实例在实例规范方面最专业,忽略其上下文(而不是使用最严格的上下文,这是什么我们需要这里。)
有什么办法吗?
答案 0 :(得分:9)
事实证明你可以。说真的,我开始认为在Haskell中一切皆有可能......你可以使用最近公布的implementation, for example方法的结果。我使用的代码类似于constraint-unions
编写的代码。不确定我是以最好的方式做到了,只是尝试应用提出的方法的概念:
{-# 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
它使用适当的功能,具体取决于a
是Ord
还是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
答案 1 :(得分:8)
我会考虑两个选择:
您可以名义上在任何地方使用slow_func
,但让重写规则尽可能优化它。例如,
import Data.List
slowFunc :: Eq a => [a] -> [a]
slowFunc = nub
fastFunc :: Ord a => [a] -> [a]
fastFunc = map head . group . sort
main = print . sum . slowFunc $ round <$> [sin x * n | x<-[0..n]]
where n = 100000
很慢(呃):
$ ghc -O2 Nub.hs && time ./Nub
[1 of 1] Compiling Main ( Nub.hs, Nub.o )
Linking Nub ...
-3670322
real 0m51.875s
user 0m51.867s
sys 0m0.004s
但如果我们添加(不更改任何内容)
{-# NOINLINE slowFunc #-}
{-# RULES "slowFunc/Integer" slowFunc = fastFunc :: [Integer] -> [Integer] #-}
然后
$ ghc -O2 Nub.hs && time ./Nub
[1 of 1] Compiling Main ( Nub.hs, Nub.o )
Linking Nub ...
-3670322
real 0m0.250s
user 0m0.245s
sys 0m0.004s
重写规则有点难以依赖(内联只是可以阻碍的一件事),但至少你可以确定与slowFunc
一起运行的东西会让工作< / em>(可能还不够快)但绝对不会在一些丢失的实例问题中丢失。另一方面,您还应该确保slowFunc
和fastFunc
实际上表现相同 - 在我的示例中,实际上并未给出! (但它可以很容易地相应修改)。
正如Alec在评论中强调的那样,您需要为要快速制作的每种类型添加重写规则。好处是,这可以在代码完成之后完成,并且确切地说,分析表明它很重要,性能方面。
这是一个可靠的解决方案:弃用任何全能实例,而不是为每种类型决定什么是合适的。
instance Func Int where
as_fast_as_possible_func = fast_func
instance Func Double where
as_fast_as_possible_func = fast_func
...
instance Func (Complex Double) where
as_fast_as_possible_func = slow_func
您可以通过将更常见的版本设为默认值来保存一些重复的行:
{-# LANGUAGE DefaultInstances #-}
class Func a where
as_fast_as_possible_func :: [a] -> [a]
default as_fast_as_possible_func :: Ord a => [a] -> [a]
as_fast_as_possible_func = fast_func
instance Func Int
instance Func Double
...
instance Func (Complex Double) where
as_fast_as_possible_func = slow_func