ely*_*ely 10 haskell pattern-matching typeclass
这是关于Haskell中多个调度的问题.
下面我使用术语"符合[类类]"来表示"类型是[类类]的实例",因为类型类通常类似于接口,所以直观地想到一个具体的东西,比如实际的Int值由于其类型实现了属于该接口/类型类所需的任何内容,因此与接口/类型类"兼容".
考虑想使一个单一的幂函数,将工作无论是调用的例子Floating论证,Num,Integral或什么的,它的工作原理是使用由参数类型实现的类型等级来选择预先存在的幂函数打电话.
该函数(^)具有类型(^) :: (Integral b, Num a) => a -> b -> a,函数(**)具有类型(**) :: Floating a => a -> a -> a.
假设我想创建一个my_pow接受Num兼容的第一个参数和一个Num兼容的第二个参数的函数.
如果两个参数都Floating符合,那么它将调用(**); 如果第二个参数仅仅Integral符合,它将调用(^); 和任何其他情况将给出模式匹配错误.
我天真的第一次尝试是像类值构造函数一样处理类型类,并尝试在函数定义中进行模式匹配:
my_pow :: (Num a, Num b) => a -> b -> a
my_pow (Floating x) (Floating y) = x ** y
my_pow x (Integral y) = x ^ y
Run Code Online (Sandbox Code Playgroud)
但这会给出错误:
tmp.hs:25:6: Not in scope: data constructor `Floating'
tmp.hs:25:19: Not in scope: data constructor `Floating'
tmp.hs:26:8: Not in scope: data constructor `Integral'
Run Code Online (Sandbox Code Playgroud)
可能意味着我不能将类类作为值构造函数处理,这并不奇怪.
但随后谷歌搜索如何模式匹配的参数的特定类型属性比函数定义中的类型类约束更具体,并没有产生任何明确的答案.
制作这种多态的首选方法是什么 - 实际上是一种调度模式,其中函数总体上放宽了类型类约束,但后来通过模式匹配来定义更具体的类型类约束,以便它将调度给其他的任何情况.其他功能.
Chr*_*kle 14
以您描述的方式对类型进行"模式匹配"的常规方法是使用类型类实例.对于具体类型,这很容易使用MultiParamTypeClasses; 这就是Haskell实现多个调度的方式.
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, OverlappingInstances #-}
module SO26303353 where
class (Num a, Num b) => Power a b where
my_pow :: a -> b -> a
instance Power Double Double where
my_pow = (**)
instance Num a => Power a Integer where
my_pow = (^)
Run Code Online (Sandbox Code Playgroud)
这很好用.它或多或少是惯用的Haskell,除了那些(**)并且(^)是不同的操作,有些人可能会反对模糊区别.
但是,你要求更精细的东西.你想不仅对多种调度类型,但在类型类.这是一个显着不同和更强大的事情.特别是,它适用于所有可能具有Floating或Intergral甚至尚未编写的类型的类型!这是理想的写作方式:
instance (Floating a) => Power a a where
my_pow = (**)
instance (Num a, Integral b) => Power a b where
my_pow = (^)
Run Code Online (Sandbox Code Playgroud)
但是,这不起作用,因为约束求解器不会回溯,并且在选择实例时不考虑实例约束.所以my_pow不起作用,例如,有两个Ints:
ghci> :t my_pow :: Int -> Int -> Int
No instance for (Floating Int)
Run Code Online (Sandbox Code Playgroud)
发生这种情况是因为"更具体"的Power a a实例匹配,因为这两种类型相同.GHC然后施加Floating约束a,并且当它不能满足它时barfs.然后它不会回溯并尝试Power a b实例.
它可能会或可能无法破解周围采用了先进的类型系统功能的限制,但我不认为你可以永远做一个简易替换为两个(**),并(^)在当前哈斯克尔.
(请注意,我们在这里偏离了Q&A格式.)
在重读你的问题和评论时,我注意到你以一种我不熟悉的方式使用"发送"一词.一个快速的谷歌出现了关于双重调度和访问者设计模式的文章.那是你来自哪里?它们看起来有点像你想要做的 - 根据其参数的类型编写一个完全不同的函数.我想在这个答案中添加一些内容,这可能有助于磨练你惯用的Haskell.(或者可能只是脱节漫步.)
Haskell通常忽略了"运行时类型"的概念.即使在@ Cirdec更详细的答案中,所有类型都是静态知道的,"在编译时".(使用REPL,ghci不会改变事物,除了"编译时间"变得模糊不清.)事实上,关于"在运行时"发生的事情的直觉在Haskell中通常与其他语言不同,尤其是因为GHC执行攻击性优化.
惯用的Haskell建立在参数多态的基础之上; 任何类型的函数都replicate :: Int -> a -> [a]完全相同.因此,我们在不必查看其实现的情况下了解了很多内容.这种态度非常有用,它深深地感染了Haskell程序员的大脑.您会注意到我和许多其他Haskell程序员都对类型注释感到疯狂,特别是在像这样的论坛中.静态类型非常有意义.(关键词:自由定理.)(这与你的问题没有直接关系.)areplicate
Haskell使用类型类来允许ad hoc多态.在我看来,'ad hoc'指的是函数的实现对于不同类型可能是不同的.这对数值类型来说当然是至关重要的,并且多年来以无数种方式应用.但重要的是要理解所有内容仍然是静态类型的,即使是类型类也是如此.要实际评估任何类型类函数 - 从中获取值 - 您需要最终选择特定类型.(对于数字类型,默认规则经常为您选择它.)您当然可以组合使用以产生另一个多态函数(或值).
从历史上看,类型类被严格地视为函数重载的机制,在几个不同函数具有相同名称的意义上.换句话说,而不是,我们有一个名字:.但它仍然基本上是相同的想法:有一堆完全不同的函数被称为.(现在我们倾向于用"法则"来讨论类型类,但这是一个不同的主题.)通常没有使用类似函数或甚至非基本函数的文字调度.addInt :: Int -> Int -> IntaddFloat :: Float -> Float -> Float(+) :: Num a => a -> a -> a(+)(+)
是的,类型类有点像接口,但是不允许OOP思维模式过于尖锐.如果你正在编写类似的类型的函数Num a => a -> a,期望你知道的唯一的事情a是它是一个实例Num.你不能在窗帘后面看,就像它一样.(没有作弊.这很难.)操纵类型值的唯一方法a是使用完全多态函数和其他Num函数.特别是,您无法确定是否a也是某个其他类的实例.
我们一直在玩的各种编译器扩展模糊了这个模型,因为我们现在可以编写类型级函数.但不要将其与动态调度混淆.
哦,顺便说一句,Haskell支持动态类型.见Data.Dymamic.老实说,除了与其他语言互操作之外,我从来没有真正看到它的用处.(我愿意做错.)典型的"访客模式"问题可以通过其他方式实现.
就像Christian Conkle暗示的那样,我们可以使用更高级的类型系统功能来确定一个类型是否有一个IntegralorFloating实例。我们将尝试确定第二个参数是否有Integral实例。在此过程中,我们将使用大量语言扩展,但仍然离我们的目标有些距离。我将介绍以下使用它们的语言扩展
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverlappingInstances #-}
Run Code Online (Sandbox Code Playgroud)
首先,我们将创建一个类,该类将尝试从类型的上下文(是否有Integral实例)中捕获信息并将其转换为我们可以匹配的类型。这需要FunctionalDependencies扩展说明flag可以从类型中唯一确定a。它还需要MultiParamTypeClasses.
class IsIntegral a flag | a -> flag
Run Code Online (Sandbox Code Playgroud)
我们将使用两种类型来flag表示类型何时有 ( HTrue) 或没有 ( HFalse)Integral实例。这使用EmptyDataDecls扩展名。
data HTrue
data HFalse
Run Code Online (Sandbox Code Playgroud)
我们将提供一个默认值 - 当没有一个IsIntegral实例a可以强制flag成为除HFalse我们提供的实例之外的其他东西时HFalse。这就要求TypeFamilies,FlexibleInstances和UndecidableInstances扩展。
instance (flag ~ HFalse) => IsIntegral a flag
Run Code Online (Sandbox Code Playgroud)
我们真正想做的是说每个a有Integral a实例的都有一个IsIntegral a HTrue实例。不幸的是,如果我们添加一个instance (Integral a) => IsIntegral a HTrue实例,我们将处于 Christian 描述的相同情况。第二个实例将被优先使用,当Integral遇到约束时,它将被添加到上下文中而不会回溯。相反,我们需要自己列出所有Integral类型。这就是我们未能达到目标的地方。(我跳过了基本 Integral类型,System.Posix.Types因为它们在所有平台上的定义不同)。
import Data.Int
import Data.Word
import Foreign.C.Types
import Foreign.Ptr
instance IsIntegral Int HTrue
instance IsIntegral Int8 HTrue
instance IsIntegral Int16 HTrue
instance IsIntegral Int32 HTrue
instance IsIntegral Int64 HTrue
instance IsIntegral Integer HTrue
instance IsIntegral Word HTrue
instance IsIntegral Word8 HTrue
instance IsIntegral Word16 HTrue
instance IsIntegral Word32 HTrue
instance IsIntegral Word64 HTrue
instance IsIntegral CUIntMax HTrue
instance IsIntegral CIntMax HTrue
instance IsIntegral CUIntPtr HTrue
instance IsIntegral CIntPtr HTrue
instance IsIntegral CSigAtomic HTrue
instance IsIntegral CWchar HTrue
instance IsIntegral CSize HTrue
instance IsIntegral CPtrdiff HTrue
instance IsIntegral CULLong HTrue
instance IsIntegral CLLong HTrue
instance IsIntegral CULong HTrue
instance IsIntegral CLong HTrue
instance IsIntegral CUInt HTrue
instance IsIntegral CInt HTrue
instance IsIntegral CUShort HTrue
instance IsIntegral CShort HTrue
instance IsIntegral CUChar HTrue
instance IsIntegral CSChar HTrue
instance IsIntegral CChar HTrue
instance IsIntegral IntPtr HTrue
instance IsIntegral WordPtr HTrue
Run Code Online (Sandbox Code Playgroud)
我们的最终目标是能够为以下类提供适当的实例
class (Num a, Num b) => Power a b where
pow :: a -> b -> a
Run Code Online (Sandbox Code Playgroud)
我们希望匹配类型以选择要使用的代码。我们将创建一个带有额外类型的类来保存是否b为Integral类型的标志。pow'让类型推断选择正确pow'使用的额外参数。
class (Num a, Num b) => Power' flag a b where
pow' :: flag -> a -> b -> a
Run Code Online (Sandbox Code Playgroud)
现在我们将编写两个实例,一个用于何时b存在Integral,一个用于何时不存在。When bis not Integral,我们只能提供一个 whena和isb相同的实例。
instance (Num a, Integral b) => Power' HTrue a b where
pow' _ = (^)
instance (Floating a, a ~ b) => Power' HFalse a b where
pow' _ = (**)
Run Code Online (Sandbox Code Playgroud)
现在,每当我们可以判断,如果b是Integral用IsIntegral,并且可以提供Power'对于结果来说,我们可以提供Power这是我们的目标实例。这需要ScopedTypeVariables扩展来获取额外参数的正确类型pow'
instance (IsIntegral b flag, Power' flag a b) => Power a b where
pow = pow' (undefined::flag)
Run Code Online (Sandbox Code Playgroud)
实际上使用这些定义需要OverlappingInstances扩展。
main = do
print (pow 7 (7 :: Int))
print (pow 8.3 (7 :: Int))
print (pow 1.2 (1.2 :: Double))
print (pow 7 (7 :: Double))
Run Code Online (Sandbox Code Playgroud)
您可以在 HaskellWiki 上的高级重叠文章中阅读有关如何使用FunctionalDependencies或TypeFamilies避免重叠实例中的重叠的另一说明。
| 归档时间: |
|
| 查看次数: |
1747 次 |
| 最近记录: |