F# 和 Scala 中的 ADT 之间的主要区别是什么?有什么 F# 的 ADT 可以做但 Scala 的 ADT 不能(反之亦然)的事情吗?
2 回答
从概念上讲,我认为这两种语言都提供了相同的功能——在 F# 中,您可以使用可区分联合声明 ADT ,在 Scala 中,您可以使用案例类。Scala 中使用类的声明可能会比 F# 版本长一点(正如殷朱指出的那样),但是您可以在两种语言中使用具有相似优雅的模式匹配。
这是一个简化术语的示例(来自本文):
def simplify(term: Term) = term match {
case Mul(Num(0), x) => Num(0)
case Mul(Num(1), x) => x
case _ => term
}
F# using 中的相同代码match
看起来非常相似:
let simplify term =
match term with
| Mul(Num(0), x) -> Num(0)
| Mul(Num(1), x) -> x
| _ -> term
差异我认为在更高级(相关)功能方面存在一些差异。
在 Scala 中,每个 case 也是一种类型,因此您可以例如定义一个采用
Num
orMul
作为参数的方法。在 F# 中,这是不可能的,因为Num
andMul
只是 type 的构造函数Term
。我想这有时可能很有用,但大多数时候,Term
无论如何你都会使用类型的值。与前一点相关 - 在 Scala 中,您还可以为个别情况定义方法。例如,您可以在
Num
类中定义一个方法。在 F# 中,所有成员都必须是该Term
类型的成员。在 F# 中,您可以使用活动模式来隐藏类型的内部表示(例如,从模块中导出它时)。这对于库设计非常有用。例如,您可以定义活动模式:
val (|Mul|_|) // return Some(..) if Term represents multiplication val (|Num|_|) // return Some(..) if Term represents number
内部表示可以随时间变化而不会影响库接口,因此您可以例如实现这样的接口:
type Term = Binary of string * Term * Term | Num of int let (|Num|_|) = function Num n -> Some n | _ -> None let (|Mul|_|) = function Binary("*", a, b) -> Some(a, b) | _ -> None
首先,没有一种语言可以,另一种语言不能。
不同的是风格。
我读了一点 Scala,但不会写。我认为二叉搜索树是比较 ADT 风格的一个很好的例子。
这是来自http://aperiodic.net/phil/scala/s-99/的代码:
package binarytree {
sealed abstract class Tree[+T]
case class Node[+T](value: T, left: Tree[T], right: Tree[T]) extends Tree[T] {
override def toString = "T(" + value.toString + " " + left.toString + " " + right.toString + ")"
}
case object End extends Tree[Nothing] {
override def toString = "."
}
object Node {
def apply[T](value: T): Node[T] = Node(value, End, End)
}
}
我们可以看到风格相当OO。
以下是我的 Treap(平衡二叉搜索树)代码的主要部分:
module Treap =
type node<'T> = {left: treap<'T>; right: treap<'T>; value: 'T; priority:int}
and treap<'T> = Empty | Node of node<'T>
let rec lookup (k:'a) (t:'a treap) =
match t with
| Empty -> None
| Node(n) ->
if k = n.value then Some(k)
elif k < n.value then lookup k (n.left)
else lookup k (n.right)
let add (k:'a) (p:int) (tree:'a treap) =
let rotate xv xp yv yp a b c =
if xp < yp then
{value=xv; priority=xp; left=a; right=Node({value=yv;priority=yp;left=b;right=c})}
else
{value=yv; priority=yp; right=c; left=Node({value=xv; priority=xp; left=a; right=b})}
let rec addNode (k:'a) (p:int) (tree:'a treap) =
match tree with
| Empty -> {value=k; priority=p; left=Empty; right=Empty}, false
| Node(n) ->
if k=n.value then
n, true
elif k<n.value then
let {value=xv; priority=xp; left=a; right=b}, dup = addNode k p (n.left)
(rotate xv xp (n.value) (n.priority) a b (n.right)), dup
else
let {value=yv; priority=yp; left=b; right=c}, dup = addNode k p (n.right)
(rotate (n.value) (n.priority) yv yp (n.left) b c), dup
let n, dup = addNode k p tree
Node(n)
我认为 Scala 也可以用这种风格编写。然而,Scala 更加面向对象。