在介绍f#(即列表)中的预定义数据类型以及如何对列表或序列中的元素求和时,我试图学习如何使用用户定义的数据类型。假设我创建了一种数据类型,将其称为list1:
type list1 =
A
| B of int * list1
位置:
所以1,2,3,4将用list1值表示:
B(1, B(2, B(3, B(4, A))))
从Wikibook中,我了解到通过列表,我可以通过以下操作对元素求和:
let List.sum [1; 2; 3; 4]
但是我该如何对用户定义的数据类型的元素求和?任何提示将不胜感激。
编辑:我可以利用匹配运算符:
let rec sumit (l: ilist) : int =
match l with
| (B(x1, A)) -> x1
| (B(x1, B(x2, A))) -> (x1+x2)
sumit (B(3, B(4, A)))
我得到:
val it : int = 7
我怎样才能做到,如果我有2个以上的整数,它仍会把元素求和(即(B(3,B(4,B(5,A))))等于12?
答案 0 :(得分:5)
一种解决此类问题的好方法是将您的算法以单词形式或伪代码形式写出,然后在确定算法后将其转换为F#。在这种情况下,您想对列表求和,如下所示:
弄清楚算法的第一步是仔细定义问题的规格。我想要一种算法来总结我的自定义列表类型。 确切地是什么意思?或者,更具体地说,这对我的自定义列表类型可以具有的两种不同类型的值(A和B)究竟意味着什么?好吧,让我们一次看看它们。如果列表的类型为A,则表示一个空列表,因此我需要确定一个空列表的总和。空列表的总和的最明智的值为0,因此规则为“如果列表为A,则总和为0”。现在,如果列表的类型为B,那么该列表的总和是什么意思?好吧,B型列表的总和就是它的int值加上子列表的总和。
因此,对于list1
可以具有的两种类型,我们现在都有一个“求和”规则。如果为A,则总和为0。如果为B,则总和为(值+子列表的总和)。该规则几乎逐字转换为F#代码!
let rec sum (lst : list1) =
match lst with
| A -> 0
| B (value, sublist) -> value + sum sublist
关于此代码,我需要注意几件事。首先,rec
关键字是您以前可能看过或可能从未见过的一件事(因为您似乎是F#初学者)。在编写递归函数时,这是必需的:由于有关F#解析器实现方式的内部细节,如果要调用函数本身,则必须在声明函数名称和参数时提前声明。其次,这不是编写sum
函数的最佳方法,因为它实际上不是尾部递归的,这意味着如果您尝试对一个真正的求和求和,它可能会抛出StackOverflowException, 真的很长的清单。在学习F#的这一点上,您也许现在不必担心这一点,但是最终您将学到一种有用的技术,可以将非尾递归函数转换为尾递归函数。它涉及添加一个通常称为“累加器”的额外参数(有时缩写为acc
),并且上述sum
函数的正确尾递归版本应如下所示:
let sum (lst : list1) =
let rec tailRecursiveSum (acc : int) (lst : list1) =
match lst with
| A -> acc
| B (value, sublist) -> tailRecursiveSum (acc + value) sublist
tailRecursiveSum 0 lst
如果您已经可以理解这一点,那就太好了!如果您尚未到此为止,请在研究完尾递归后将此答案添加为书签,然后再返回,因为此技术(通过使用内部函数将非尾递归函数转换为尾递归函数函数和一个累加器参数)是非常有价值的,它在F#编程中具有各种应用程序。
答案 1 :(得分:2)
除了尾递归,泛型编程对于功能学习者来说可能是一个重要的概念。如果只能容纳整数值,为什么还要创建一个自定义数据类型呢?
可以将列表中所有元素的总和抽象为对表中所有元素和以初始状态准备好的累加器的加法运算符的重复应用。这可以概括为功能折叠:
sum
要使(+)
函数泛型也需要一点黑魔法,称为静态解析类型参数。签名不是很漂亮,它实际上告诉您它期望类型上的let inline sum xs = fold (+) Unchecked.defaultof<_> xs
// val inline sum :
// xs: ^a list1 -> ^b
// when ( ^b or ^a) : (static member ( + ) : ^b * ^a -> ^b)
B(1, B(2, B(3, B(4, A))))
|> sum
// val it : int = 10
运算符能够成功编译。
void DoesNothing(){}
void OnlyCalledOnce(){
//lines of code
}