你如何在Haskell中进行泛型编程?

时间:2021-09-02 17:01:54

Coming from C++, I find generic programming indispensable. I wonder how people approach that in Haskell?

来自C ++,我发现通用编程是不可或缺的。我想知道人们如何在Haskell中接近它?

Say how do write generic swap function in Haskell?

说如何在Haskell中编写通用交换函数?

Is there an equivalent concept of partial specialization in Haskell?

在Haskell中是否存在部分特化的等价概念?

In C++, I can partially specialize the generic swap function with a special one for a generic map/hash_map container that has a special swap method for O(1) container swap. How do you do that in Haskell or what's the canonical example of generic programming in Haskell?

在C ++中,我可以部分地将泛型交换函数专门用于通用map / hash_map容器,该容器具有O(1)容器交换的特殊交换方法。你如何在Haskell中做到这一点,或者Haskell中泛型编程的典型例子是什么?

5 个解决方案

#1


This is closely related to your other question about Haskell and quicksort. I think you probably need to read at least the introduction of a book about Haskell. It sounds as if you haven't yet grasped the key point about it which is that it bans you from modifying the values of existing variables.

这与您关于Haskell和quicksort的其他问题密切相关。我想你可能至少需要阅读一本关于Haskell的书的介绍。听起来好像你还没有掌握它的关键点,它禁止你修改现有变量的值。

Swap (as understood and used in C++) is, by its very nature, all about modifying existing values. It's so we can use a name to refer to a container, and replace that container with completely different contents, and specialize that operation to be fast (and exception-free) for specific containers, allowing us to implement a modify-and-publish approach (crucial for writing exception-safe code or attempting to write lock-free code).

交换(正如在C ++中所理解和使用的)就其本质而言,都是关于修改现有值的。我们可以使用名称来引用容器,并用完全不同的内容替换该容器,并将该操作专门用于特定容器的快速(和无异常),从而允许我们实现修改和发布方法(对于编写异常安全代码或尝试编写无锁代码至关重要)。

You can write a generic swap in Haskell, but it would probably take a pair of values and return a new pair containing the same values with their positions reversed, or something like that. Not really the same thing, and not having the same uses. It wouldn't make any sense to try and specialise it for a map by digging inside that map and swapping its individual member variables, because you're just not allowed to do things like that in Haskell (you can do the specialization, but not the modifying of variables).

您可以在Haskell中编写一个通用交换,但它可能需要一对值并返回一个包含相同值且其位置相反的新对,或类似的东西。不是一回事,也没有相同的用途。通过挖掘内部地图并交换其各个成员变量来尝试将其专门化为地图是没有任何意义的,因为你不能在Haskell中做这样的事情(你可以做专业化,但不能修改变量)。

Suppose we wanted to "measure" a list in Haskell:

假设我们想在Haskell中“测量”一个列表:

measure :: [a] -> Integer

That's a type declaration. It means that the function measure takes a list of anything (a is a generic type parameter because it starts with a lowercase letter) and returns an Integer. So this works for a list of any element type - it's what would be called a function template in C++, or a polymorphic function in Haskell (not the same as a polymorphic class in C++).

这是一种类型声明。这意味着函数度量采用任何事物的列表(a是泛型类型参数,因为它以小写字母开头)并返回一个Integer。因此,这适用于任何元素类型的列表 - 它在C ++中称为函数模板,或Haskell中的多态函数(与C ++中的多态类不同)。

We can now define that by providing specializations for each interesting case:

我们现在可以通过为每个有趣的案例提供专业化来定义它:

measure [] = 0

i.e. measure the empty list and you get zero.

即测量空列表,你就得零。

Here's a very general definition that covers all other cases:

这是一个涵盖所有其他案例的非常一般的定义:

measure (h:r) = 1 + measure r

The bit in parentheses on the LHS is a pattern. It means: take a list, break off the head and call it h, call the remaining part r. Those names are then parameters we can use. This will match any list with at least one item on it.

LHS括号中的位是一种模式。这意味着:拿一个清单,中断并调用它,然后调用剩下的部分r。这些名称是我们可以使用的参数。这将匹配任何列表上至少有一个项目。

If you've tried template metaprogramming in C++ this will all be old hat to you, because it involves exactly the same style - recursion to do loops, specialization to make the recursion terminate. Except that in Haskell it works at runtime (specialization of the function for particular values or patterns of values).

如果你在C ++中尝试过模板元编程,这对你来说都是旧的,因为它涉及完全相同的样式 - 递归循环,专门化使递归终止。除了在Haskell中它在运行时工作(特定值或模式的函数的特化)。

#2


As Earwicker sais, the example is not as meaningful in Haskell. If you absolutely want to have it anyway, here is something similar (swapping the two parts of a pair), c&p from an interactive session:

正如Earwicker所说,这个例子在Haskell中并没有那么有意义。无论如何你绝对想拥有它,这里有类似的东西(交换一对的两部分),来自交互式会话的c&p:

GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude> let swap (a,b) = (b,a)
Prelude> swap("hello", "world")
("world","hello")
Prelude> swap(1,2)
(2,1)
Prelude> swap("hello",2)
(2,"hello")

#3


In Haskell, functions are as generic (polymorphic) as possible - the compiler will infer the "Most general type". For example, TheMarko's example swap is polymorphic by default in the absence of a type signature:

在Haskell中,函数尽可能是通用的(多态的) - 编译器将推断出“最常见的类型”。例如,在没有类型签名的情况下,默认情况下,TheMarko的示例交换是多态的:

*Main> let swap (a,b) = (b,a)
*Main> :t swap
swap :: (t, t1) -> (t1, t)

* Main> let swap(a,b)=(b,a)* Main>:t swap swap ::(t,t1) - >(t1,t)

As for partial specialization, ghc has a non-98 extension:
file:///C:/ghc/ghc-6.10.1/doc/users_guide/pragmas.html#specialize-pragma

至于部分特化,ghc有一个非98扩展名:file:/// C:/ghc/ghc-6.10.1/doc/users_guide/pragmas.html#specialize-pragma

Also, note that there's a mismatch in terminology. What's called generic in c++, Java, and C# is called polymorphic in Haskell. "Generic" in Haskell usually means polytypic: http://haskell.readscheme.org/generic.html
But, aboe i use the c++ meaning of generic.

另请注意,术语不匹配。在C ++,Java和C#中所谓的泛型在Haskell中称为多态。 Haskell中的“Generic”通常意味着polytypic:http://haskell.readscheme.org/generic.html但是,aboe我使用泛型的c ++含义。

#4


In Haskell you would create type classes. Type classes are not like classes in OO languages. Take the Numeric type class It says that anything that is an instance of the class can perform certain operations(+ - * /) so Integer is a member of Numeric and provides implementations of the functions necessary to be considered Numeric and can be used anywhere a Numeric is expected.

在Haskell中,您将创建类型类。类型类与OO语言中的类不同。取Numeric类型它说任何作为类实例的东西都可以执行某些操作(+ - * /),因此Integer是Numeric的成员,并提供了被认为是Numeric所必需的函数的实现,可以在任何地方使用预期数字。

Say you want to be able to foo Ints and Strings. Then you would declare Int and String to be instances of the type class Foo. Now anywhere you see the type (Foo a) you can now use Int or String.

假设您希望能够吸引Ints和Strings。然后,您将Int和String声明为类Foo的实例。现在,只要您看到类型(Foo a),就可以使用Int或String。

The reason why you can't add ints and floats directly is because add has the type (Numeric a) a -> a -> a a is a type variable and just like regular variables it can only be bound once so as soon as you bind it to Int every a in the list must be Int.

你不能直接添加整数和浮点数的原因是因为add有类型(数字a)a - > a - > aa是一个类型变量,就像常规变量一样,它只能绑定一次,所以只要你绑定它到Int中列表中的每个a必须是Int。

#5


After reading enough in a Haskell book to really understand Earwicker's answer I'd suggest you also read about type classes. I'm not sure what “partial specialization” means, but it sounds like they could come close.

在Haskell书中阅读足够的内容以真正理解Earwicker的答案后,我建议你也阅读类型类。我不确定“部分专业化”是什么意思,但听起来它们可能会接近。

#1


This is closely related to your other question about Haskell and quicksort. I think you probably need to read at least the introduction of a book about Haskell. It sounds as if you haven't yet grasped the key point about it which is that it bans you from modifying the values of existing variables.

这与您关于Haskell和quicksort的其他问题密切相关。我想你可能至少需要阅读一本关于Haskell的书的介绍。听起来好像你还没有掌握它的关键点,它禁止你修改现有变量的值。

Swap (as understood and used in C++) is, by its very nature, all about modifying existing values. It's so we can use a name to refer to a container, and replace that container with completely different contents, and specialize that operation to be fast (and exception-free) for specific containers, allowing us to implement a modify-and-publish approach (crucial for writing exception-safe code or attempting to write lock-free code).

交换(正如在C ++中所理解和使用的)就其本质而言,都是关于修改现有值的。我们可以使用名称来引用容器,并用完全不同的内容替换该容器,并将该操作专门用于特定容器的快速(和无异常),从而允许我们实现修改和发布方法(对于编写异常安全代码或尝试编写无锁代码至关重要)。

You can write a generic swap in Haskell, but it would probably take a pair of values and return a new pair containing the same values with their positions reversed, or something like that. Not really the same thing, and not having the same uses. It wouldn't make any sense to try and specialise it for a map by digging inside that map and swapping its individual member variables, because you're just not allowed to do things like that in Haskell (you can do the specialization, but not the modifying of variables).

您可以在Haskell中编写一个通用交换,但它可能需要一对值并返回一个包含相同值且其位置相反的新对,或类似的东西。不是一回事,也没有相同的用途。通过挖掘内部地图并交换其各个成员变量来尝试将其专门化为地图是没有任何意义的,因为你不能在Haskell中做这样的事情(你可以做专业化,但不能修改变量)。

Suppose we wanted to "measure" a list in Haskell:

假设我们想在Haskell中“测量”一个列表:

measure :: [a] -> Integer

That's a type declaration. It means that the function measure takes a list of anything (a is a generic type parameter because it starts with a lowercase letter) and returns an Integer. So this works for a list of any element type - it's what would be called a function template in C++, or a polymorphic function in Haskell (not the same as a polymorphic class in C++).

这是一种类型声明。这意味着函数度量采用任何事物的列表(a是泛型类型参数,因为它以小写字母开头)并返回一个Integer。因此,这适用于任何元素类型的列表 - 它在C ++中称为函数模板,或Haskell中的多态函数(与C ++中的多态类不同)。

We can now define that by providing specializations for each interesting case:

我们现在可以通过为每个有趣的案例提供专业化来定义它:

measure [] = 0

i.e. measure the empty list and you get zero.

即测量空列表,你就得零。

Here's a very general definition that covers all other cases:

这是一个涵盖所有其他案例的非常一般的定义:

measure (h:r) = 1 + measure r

The bit in parentheses on the LHS is a pattern. It means: take a list, break off the head and call it h, call the remaining part r. Those names are then parameters we can use. This will match any list with at least one item on it.

LHS括号中的位是一种模式。这意味着:拿一个清单,中断并调用它,然后调用剩下的部分r。这些名称是我们可以使用的参数。这将匹配任何列表上至少有一个项目。

If you've tried template metaprogramming in C++ this will all be old hat to you, because it involves exactly the same style - recursion to do loops, specialization to make the recursion terminate. Except that in Haskell it works at runtime (specialization of the function for particular values or patterns of values).

如果你在C ++中尝试过模板元编程,这对你来说都是旧的,因为它涉及完全相同的样式 - 递归循环,专门化使递归终止。除了在Haskell中它在运行时工作(特定值或模式的函数的特化)。

#2


As Earwicker sais, the example is not as meaningful in Haskell. If you absolutely want to have it anyway, here is something similar (swapping the two parts of a pair), c&p from an interactive session:

正如Earwicker所说,这个例子在Haskell中并没有那么有意义。无论如何你绝对想拥有它,这里有类似的东西(交换一对的两部分),来自交互式会话的c&p:

GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude> let swap (a,b) = (b,a)
Prelude> swap("hello", "world")
("world","hello")
Prelude> swap(1,2)
(2,1)
Prelude> swap("hello",2)
(2,"hello")

#3


In Haskell, functions are as generic (polymorphic) as possible - the compiler will infer the "Most general type". For example, TheMarko's example swap is polymorphic by default in the absence of a type signature:

在Haskell中,函数尽可能是通用的(多态的) - 编译器将推断出“最常见的类型”。例如,在没有类型签名的情况下,默认情况下,TheMarko的示例交换是多态的:

*Main> let swap (a,b) = (b,a)
*Main> :t swap
swap :: (t, t1) -> (t1, t)

* Main> let swap(a,b)=(b,a)* Main>:t swap swap ::(t,t1) - >(t1,t)

As for partial specialization, ghc has a non-98 extension:
file:///C:/ghc/ghc-6.10.1/doc/users_guide/pragmas.html#specialize-pragma

至于部分特化,ghc有一个非98扩展名:file:/// C:/ghc/ghc-6.10.1/doc/users_guide/pragmas.html#specialize-pragma

Also, note that there's a mismatch in terminology. What's called generic in c++, Java, and C# is called polymorphic in Haskell. "Generic" in Haskell usually means polytypic: http://haskell.readscheme.org/generic.html
But, aboe i use the c++ meaning of generic.

另请注意,术语不匹配。在C ++,Java和C#中所谓的泛型在Haskell中称为多态。 Haskell中的“Generic”通常意味着polytypic:http://haskell.readscheme.org/generic.html但是,aboe我使用泛型的c ++含义。

#4


In Haskell you would create type classes. Type classes are not like classes in OO languages. Take the Numeric type class It says that anything that is an instance of the class can perform certain operations(+ - * /) so Integer is a member of Numeric and provides implementations of the functions necessary to be considered Numeric and can be used anywhere a Numeric is expected.

在Haskell中,您将创建类型类。类型类与OO语言中的类不同。取Numeric类型它说任何作为类实例的东西都可以执行某些操作(+ - * /),因此Integer是Numeric的成员,并提供了被认为是Numeric所必需的函数的实现,可以在任何地方使用预期数字。

Say you want to be able to foo Ints and Strings. Then you would declare Int and String to be instances of the type class Foo. Now anywhere you see the type (Foo a) you can now use Int or String.

假设您希望能够吸引Ints和Strings。然后,您将Int和String声明为类Foo的实例。现在,只要您看到类型(Foo a),就可以使用Int或String。

The reason why you can't add ints and floats directly is because add has the type (Numeric a) a -> a -> a a is a type variable and just like regular variables it can only be bound once so as soon as you bind it to Int every a in the list must be Int.

你不能直接添加整数和浮点数的原因是因为add有类型(数字a)a - > a - > aa是一个类型变量,就像常规变量一样,它只能绑定一次,所以只要你绑定它到Int中列表中的每个a必须是Int。

#5


After reading enough in a Haskell book to really understand Earwicker's answer I'd suggest you also read about type classes. I'm not sure what “partial specialization” means, but it sounds like they could come close.

在Haskell书中阅读足够的内容以真正理解Earwicker的答案后,我建议你也阅读类型类。我不确定“部分专业化”是什么意思,但听起来它们可能会接近。