如果类实例可用,请使用专门的实现

JSQ*_*reD 16 haskell types typeclass

考虑以下情况:

slow_func :: Eq a  => [a] -> [a]
fast_func :: Ord a => [a] -> [a]
Run Code Online (Sandbox Code Playgroud)

我有两个功能,slow_funcfast_func.这些函数是同一个抽象函数的不同实现(它们做同样的事情),但是一个比另一个快.只有在a可以订购类型时才能实现更快的实施.有没有办法构建一个fast_func尽可能充当的函数,并以slow_func其他方式恢复?

as_fast_as_possible_func :: Eq a => [a] -> [a]
Run Code Online (Sandbox Code Playgroud)

我已经尝试过以下方法:

{-# 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
Run Code Online (Sandbox Code Playgroud)

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

Duplicate instance declarations:
  instance Ord a => Func a
    -- Defined at [...]
  instance Eq a => Func a
    -- Defined at [...]
Run Code Online (Sandbox Code Playgroud)

原因是OverlappingInstances希望其中一个实例在实例规范方面最专业,忽略其上下文(而不是使用最严格的上下文,这是我们在这里需要的).

有什么办法吗?

She*_*rsh 9

事实证明你可以.说真的,我开始认为在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
Run Code Online (Sandbox Code Playgroud)

这里的关键部分是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
Run Code Online (Sandbox Code Playgroud)

这取决于是否使用适当的功能aOrdEq.我把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
Run Code Online (Sandbox Code Playgroud)

UPDATE

我已经更新了所需的实例.代替

instance (c || Eq SlowWrapper)  where resolve = \_ r -> r
Run Code Online (Sandbox Code Playgroud)

现在它是

instance d => (Ord SlowWrapper || d) where resolve = \_ r -> r
Run Code Online (Sandbox Code Playgroud)

谢谢@rampion的解释!

  • 他只包装了这些价值观,为了演示目的摆脱了"奥德". (3认同)
  • 由于您必须手动包装值,您不妨手动调用正确的函数。 (2认同)

lef*_*out 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
Run Code Online (Sandbox Code Playgroud)

很慢(duh):

$ 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
Run Code Online (Sandbox Code Playgroud)

但如果我们添加(不改变任何东西)

{-# NOINLINE slowFunc #-}
{-# RULES "slowFunc/Integer" slowFunc = fastFunc :: [Integer] -> [Integer] #-}
Run Code Online (Sandbox Code Playgroud)

然后

$ 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
Run Code Online (Sandbox Code Playgroud)

重写规则有点难以依赖(内联只是可以阻碍的一件事),但至少你可以确定运行的东西slowFunc会继续工作(可能不够快)但绝对不会迷失在一些失踪的实例问题中.另一方面,你也应该非常确定slowFunc并且fastFunc实际上表现相同 - 在我的例子中,实际上并没有给出!(但它可以很容易地相应地修改).

正如Alec在评论中强调的那样,您需要为要快速制作的每种类型添加重写规则.好处是,这可以在代码完成之后完成,并且确切地说,分析表明它很重要,性能方面.

个别实例

这是可靠的解决方案:避免任何catch-all实例,而是为每种类型决定什么是合适的.

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
Run Code Online (Sandbox Code Playgroud)

您可以通过使更常见的版本为默认值来保存一些重复的行:

{-# 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
Run Code Online (Sandbox Code Playgroud)