Haskell TypeClass 的调度是动态的吗?
Is the dispatch of a Haskell TypeClass dynamic?
给定以下 Haskell 代码快照:
class Foo a where
bar :: a -> ...
quux :: a -> ...
...
其中 a 的值在运行时确定 - class 调度此值。
我假设编译器可以在编译时静态检查类型,并确保没有无效类型可以分派给它。
现在,如果我们将其与 Java 中的动态调度进行比较:
interface Flippable {
Flippable flip();
}
class Left implements Flippable {
Right flip();
}
class Right implements Flippable {
Left flip();
}
class Demo {
public static void main(String args[]) {
Flippable flippable = new Right();
System.out.println(flippable.flip);
}
}
假设:
- Haskell 可以分派 return 类型以及多个参数,这使得分派不同于其他语言。
我的问题是:Haskell TypeClass 的调度是动态的吗?
这取决于你所说的 "dynamic" 调度是什么意思。 haskell 中没有子类型,因此您的 Java 示例很难翻译。
如果你有类型 class,比如 Show
并且想要将不同的元素放入列表中,你可以使用存在量化:
{-# LANGUAGE ExistentialQuantification #-}
data Showable = forall a. Show a => Showable a
instance Show Showable where
show (Showable x) = show x
test :: main ()
test = let list = [Showable True, Showable "string"]
in print list
-- >>> test
-- [True,"string"]
在这里你可以说调度发生了"dynamically"。它的发生方式与 C++ 或 Java 中的方式相同。 Show
字典带有一个对象,如 C++ 中的 vtable(或 Java 中的 class definition ptr,不知道它是如何调用的)。
然而,正如 @MathematicalOrchid 解释的那样,这是一种反模式。
然而,如果您想从 Left
翻转到 Right
并返回,您可以在类型 class 定义中说明。
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FunctionalDependencies #-}
class Flippable a b | a -> b where
flip' :: Flippable b a => a -> b
newtype INL = INL Int deriving Show
newtype INR = INR Int deriving Show
instance Flippable INL INR where
flip' (INL x) = INR x
instance Flippable INR INL where
flip' (INR x) = INL x
-- >>> flip' $ INL 1
-- INR 1
-- >>> flip' $ flip' $ INL 1
-- INL 1
在这种情况下 flip'
调用已在编译时解析。
您也可以 class Flip a where flip' :: a -> Flippable
使用存在量化。然后将动态调度连续调用。一如既往,这取决于您的需求。
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE StandaloneDeriving #-}
class Flip a where
flip' :: a -> Flippable
-- Show is only for repl purposes
data Flippable = forall a. (Flip a, Show a) => Flippable a
deriving instance Show Flippable
instance Flip Flippable where
flip' (Flippable x) = flip' x
newtype INL = INL Int deriving Show
newtype INR = INR Int deriving Show
instance Flip INL where
flip' (INL x) = Flippable (INR x)
instance Flip INR where
flip' (INR x) = Flippable (INL x)
-- >>> flip' $ flip' $ flip' $ INL 1
-- Flippable (INR 1)
希望这能回答您的问题。
如果您的代码是 Haskell 2010,没有打开任何语言扩展,Haskell 实际上根本不支持 运行 时间多态性 !
这真是令人惊讶。 Haskell 感觉是一种非常多态的语言。但实际上,所有类型原则上都可以纯粹在编译时决定。 (GHC 选择不这样做,但这是一个实现细节。)
这和C++模板完全一样。当你写类似 std::vector<int>
的东西时,编译器在编译时知道所有涉及的时间。真正令人惊讶的是,您实际上 需要 真正的 运行 时间多态性!
现在,在某些情况下,您希望根据 运行 时间情况 运行 不同的代码。但是在 Haskell 中,你可以通过传递一个函数作为参数来做到这一点;您不需要创建 "class"(在 OOP 意义上)只是为了实现此目的。
现在,如果你打开一些语言扩展(最明显的是 ExistentialQuantification
),那么你就会得到真实的,运行-time 多态性。
请注意,人们这样做的主要原因似乎是您可以这样做
class Foo f where
bar :: f -> Int
baz :: f -> Bool
data AnyFoo = forall f. Foo => AnyFoo f
my_list :: [AnyFoo]
...
这被广泛认为是 Haskell 反模式。特别是,如果您向上转换 Java 中的内容以将其放入列表中,您稍后会再次 向下转换 。但是上面的代码没有提供任何沮丧的可能性。您也不能使用 运行 时间反射(因为 Haskell 也没有)。所以真的,如果你有一个 AnyFoo
的列表,你可以用它做的 只有 的事情就是调用 foo
或 bar
。那么...为什么不只存储 foo
和 bar
的结果?
data AnyFoo = AnyFoo {foo :: Int, bar :: Bool}
它可以让您做完全相同的事情,但不需要任何非标准扩展。事实上,在某些方面,它实际上更灵活一些。你现在甚至不需要一个Foo
class,你不需要需要为您可能拥有的每一种 Foo
,只是一个为其构建 AnyFoo
数据结构的函数。
给定以下 Haskell 代码快照:
class Foo a where
bar :: a -> ...
quux :: a -> ...
...
其中 a 的值在运行时确定 - class 调度此值。
我假设编译器可以在编译时静态检查类型,并确保没有无效类型可以分派给它。
现在,如果我们将其与 Java 中的动态调度进行比较:
interface Flippable {
Flippable flip();
}
class Left implements Flippable {
Right flip();
}
class Right implements Flippable {
Left flip();
}
class Demo {
public static void main(String args[]) {
Flippable flippable = new Right();
System.out.println(flippable.flip);
}
}
假设:
- Haskell 可以分派 return 类型以及多个参数,这使得分派不同于其他语言。
我的问题是:Haskell TypeClass 的调度是动态的吗?
这取决于你所说的 "dynamic" 调度是什么意思。 haskell 中没有子类型,因此您的 Java 示例很难翻译。
如果你有类型 class,比如 Show
并且想要将不同的元素放入列表中,你可以使用存在量化:
{-# LANGUAGE ExistentialQuantification #-}
data Showable = forall a. Show a => Showable a
instance Show Showable where
show (Showable x) = show x
test :: main ()
test = let list = [Showable True, Showable "string"]
in print list
-- >>> test
-- [True,"string"]
在这里你可以说调度发生了"dynamically"。它的发生方式与 C++ 或 Java 中的方式相同。 Show
字典带有一个对象,如 C++ 中的 vtable(或 Java 中的 class definition ptr,不知道它是如何调用的)。
然而,正如 @MathematicalOrchid 解释的那样,这是一种反模式。
然而,如果您想从 Left
翻转到 Right
并返回,您可以在类型 class 定义中说明。
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FunctionalDependencies #-}
class Flippable a b | a -> b where
flip' :: Flippable b a => a -> b
newtype INL = INL Int deriving Show
newtype INR = INR Int deriving Show
instance Flippable INL INR where
flip' (INL x) = INR x
instance Flippable INR INL where
flip' (INR x) = INL x
-- >>> flip' $ INL 1
-- INR 1
-- >>> flip' $ flip' $ INL 1
-- INL 1
在这种情况下 flip'
调用已在编译时解析。
您也可以 class Flip a where flip' :: a -> Flippable
使用存在量化。然后将动态调度连续调用。一如既往,这取决于您的需求。
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE StandaloneDeriving #-}
class Flip a where
flip' :: a -> Flippable
-- Show is only for repl purposes
data Flippable = forall a. (Flip a, Show a) => Flippable a
deriving instance Show Flippable
instance Flip Flippable where
flip' (Flippable x) = flip' x
newtype INL = INL Int deriving Show
newtype INR = INR Int deriving Show
instance Flip INL where
flip' (INL x) = Flippable (INR x)
instance Flip INR where
flip' (INR x) = Flippable (INL x)
-- >>> flip' $ flip' $ flip' $ INL 1
-- Flippable (INR 1)
希望这能回答您的问题。
如果您的代码是 Haskell 2010,没有打开任何语言扩展,Haskell 实际上根本不支持 运行 时间多态性 !
这真是令人惊讶。 Haskell 感觉是一种非常多态的语言。但实际上,所有类型原则上都可以纯粹在编译时决定。 (GHC 选择不这样做,但这是一个实现细节。)
这和C++模板完全一样。当你写类似 std::vector<int>
的东西时,编译器在编译时知道所有涉及的时间。真正令人惊讶的是,您实际上 需要 真正的 运行 时间多态性!
现在,在某些情况下,您希望根据 运行 时间情况 运行 不同的代码。但是在 Haskell 中,你可以通过传递一个函数作为参数来做到这一点;您不需要创建 "class"(在 OOP 意义上)只是为了实现此目的。
现在,如果你打开一些语言扩展(最明显的是 ExistentialQuantification
),那么你就会得到真实的,运行-time 多态性。
请注意,人们这样做的主要原因似乎是您可以这样做
class Foo f where
bar :: f -> Int
baz :: f -> Bool
data AnyFoo = forall f. Foo => AnyFoo f
my_list :: [AnyFoo]
...
这被广泛认为是 Haskell 反模式。特别是,如果您向上转换 Java 中的内容以将其放入列表中,您稍后会再次 向下转换 。但是上面的代码没有提供任何沮丧的可能性。您也不能使用 运行 时间反射(因为 Haskell 也没有)。所以真的,如果你有一个 AnyFoo
的列表,你可以用它做的 只有 的事情就是调用 foo
或 bar
。那么...为什么不只存储 foo
和 bar
的结果?
data AnyFoo = AnyFoo {foo :: Int, bar :: Bool}
它可以让您做完全相同的事情,但不需要任何非标准扩展。事实上,在某些方面,它实际上更灵活一些。你现在甚至不需要一个Foo
class,你不需要需要为您可能拥有的每一种 Foo
,只是一个为其构建 AnyFoo
数据结构的函数。