为什么在F#中有这么多针对不同类型的`map`函数


9

我正在学习F#。我从Haskell开始FP,对此感到很好奇。

由于F#是.NET语言,因此Mappable像haskell Functor类型类那样声明接口似乎更合理。

在此处输入图片说明

但是,如上图所示,F#函数是单独实现的。这种设计的设计目的是什么?对我来说,Mappable.map为每种数据类型引入和实现此方法将更为方便。


这个问题不属于SO。这不是编程问题。我建议您在F#Slack或其他讨论论坛中提问。
Bent Tranberg

5
@BentTranberg The community is here to help you with specific coding, algorithm, or language problems.只要符合其他条件,大手笔阅读也将涉及语言设计问题。
kaefer

3
长话短说,F#没有类型类,因此需要map为每个集合类型重新实现以及其他常见的高阶函数。接口几乎无济于事,因为它仍然需要每种集合类型提供单独的实现。
dumetrulo

Answers:


19

是的,表面上一个非常简单的问题。但是,如果您花时间考虑到底,那么您将深入到无法估量的类型理论中。类型理论也注视着您。

首先,当然,您已经正确地发现F#没有类型类,这就是原因。但是你建议一个接口Mappable。好吧,让我们调查一下。

假设我们可以声明这样的接口。您能想象它的签名会是什么样子吗?

type Mappable =
    abstract member map : ('a -> 'b) -> 'f<'a> -> 'f<'b>

f实现接口的类型在哪里。等一下!F#也没有!这f是类型较高的类型变量,而F#根本没有类型较高的变量。没有办法声明一个函数f : 'm<'a> -> 'm<'b>或类似的东西。

但是好吧,我们也克服了这一障碍。现在我们有一个接口Mappable,可以通过实现ListArraySeq,以及厨房水槽。可是等等!现在我们有了方法而不是函数,而且方法的组合不好!让我们看一下将42加到嵌套列表的每个元素中:

// Good ol' functions:
add42 nestedList = nestedList |> List.map (List.map ((+) 42))

// Using an interface:
add42 nestedList = nestedList.map (fun l -> l.map ((+) 42))

看:现在我们必须使用lambda表达式!没有办法通过.map实现作为值给另一个函数。实际上是“函数作为值”的结尾(是的,我知道,在这个示例中使用lambda看起来并不糟糕,但是请相信我,它变得非常丑陋)

但是,等等,我们还没有完成。现在是方法调用,类型推断不起作用!由于.NET方法的类型签名取决于对象的类型,因此编译器无法推断两者。实际上,这是新手与.NET库进行互操作时遇到的一个非常普遍的问题。唯一的解决方法是提供类型签名:

add42 (nestedList : #Mappable) = nestedList.map (fun l -> l.map ((+) 42))

哦,但这还不够!即使我已经为其提供了签名nestedList,也没有为lambda的parameter提供签名l。这种签名应该是什么?你会说应该fun (l: #Mappable) -> ...吗?哦,现在我们终于可以对N个类型进行排名了,如您所见,它#Mappable是“任何类型'a诸如此类的'a :> Mappable -即本身是通用的lambda表达式。

或者,我们可以返回更高种类的对象,并nestedList更精确地声明类型:

add42 (nestedList : 'f<'a<'b>> where 'f :> Mappable, 'a :> Mappable) = ...

但是好吧,让我们暂时搁置类型推断,然后回到lambda表达式以及我们现在如何不能将其map作为值传递给另一个函数。假设我们对语法进行了一些扩展,以允许Elm对记录字段执行以下操作:

add42 nestedList = nestedList.map (.map ((+) 42))

会是什么类型.map?就像在Haskell中一样,它必须是受约束的类型!

.map : Mappable 'f => ('a -> 'b) -> 'f<'a> -> 'f<'b>

哇好 撇开.NET甚至不允许此类类型存在的事实,有效地,我们只需返回类型类即可!

但是有一个原因,就是F#首先没有类型类。上面已说明了该原因的许多方面,但更简单的表达方式是:简单性

如您所见,这是一个毛线球。一旦有了类型类,就必须具有约束,更高种类,N级(或至少2级),并且在不知道它之前,您就要求强制性类型,类型函数,GADT和所有其余的。

但是Haskell确实为所有好东西付出了代价。事实证明,没有很好的方法来推断所有这些东西。种类较多的类型可以工作,但约束已经不起作用。等级N-甚至不要梦想。即使工作正常,您也会遇到必须要拥有博士学位才​​能理解的类型错误。这就是为什么在Haskell中会鼓励您在所有内容上加上类型签名的原因。好吧,不是所有的东西 -而是几乎所有的东西。以及您不放置类型签名的地方(例如,在内部letwhere)-令人惊讶的是,那些地方实际上是单色的,因此您实质上回到了简单的F#领域。

另一方面,在F#中,类型签名很少见,主要用于文档或.NET互操作。除了这两种情况,您可以用F#编写整个大型复杂程序,而不必一次使用类型签名。类型推断可以很好地工作,因为没有什么太复杂或模棱两可的处理方式了。

这是F#与Haskell相比的最大优势。是的,Haskell可让您以非常精确的方式表达超级复杂的内容,这很好。但是F#就像Python或Ruby一样让您变得一厢情愿,如果遇到问题,仍然让编译器抓住您。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.