前几天我一直在学习f#,写了一个小项目,最后工作(当然是在SO的帮助下)。
我正在努力学习尽可能惯用,这基本上意味着我试图不改变我的数据结构。这让我付出了很多努力:-) 在我寻找惯用函数式编程时,我一直在尝试使用尽可能多的列表,元组和记录,而不是对象。但是,“praticality胜过纯洁”,所以这次我用对象改写我的小项目。
我认为你可以给我一些建议,当然我对“良好的函数式编程设计”的想法还没有很好地定义。
例如,我必须修改树的节点,同时修改两个不同级别(L和L + 1)的状态。我已经能够在不改变数据的情况下做到这一点,但我需要很多“内部”和“辅助”功能,有累加器等等。由于需要以涉及的方式修改我的数据结构,因此能够清楚地表达算法的美妙感觉已经丢失。这在命令式语言中非常容易,例如:只需取消引用指向相关节点的指针,修改它们的状态并进行迭代。 当然我没有正确设计我的结构,因此我现在正在尝试OOP方法。
我看过SICP,如何设计程序,并找到了C. Okasaki的论文(“纯粹的功能数据结构”),但SICP和HTDP的例子与我的做法相似,或者我可能我无法完全理解它们。另一方面,论文对我来说有点太难了: - )
您如何看待我所经历的这种“紧张”?我是否过于严格地解释“永不变异数据”?你能给我一些资源吗?
提前致谢, 弗朗西斯科
答案 0 :(得分:6)
当涉及到“树更新”时,我认为你总是可以非常优雅地做到这一点 使用catamorphisms(折叠在树上)。我有一个很长的博客系列, 以下大多数示例代码来自part 4 of the series。
首次学习时,我发现最好将注意力放在特定的小型混凝土上 问题陈述。根据您的描述,我发明了以下问题:
你有一个二叉树,每个节点包含一个“名称”和一个“数量”(可以 把它想象成银行账户或其他一些东西。我想写一个函数 这可以告诉别人从他的每一个直接“窃取”一定数量 儿童。这是描述我的意思的图片:
在左边我有一棵原始树。中间的例子显示了我想要的结果节点 'D'被要求从他的每个孩子身上偷走'10'。正确的例子 如果相反我在原始例子中要求'F'窃取'30',则显示出所需的结果。
请注意,我使用的树结构将是不可变的,而红色则是 图表指定相对于原始树的“新树节点”。那是黑色的 节点与原始树结构共享(Object.ReferenceEquals为1) 另一个)。
现在,假设一个典型的树结构,如
type Tree<'T> = //'
| Node of 'T * Tree<'T> * Tree<'T> //'
| Leaf
我们将原始树表示为
let origTree = Node(("D",1000),
Node(("B",1000),
Node(("A",1000),Leaf,Leaf),
Node(("C",1000),Leaf,Leaf)),
Node(("F",1000),
Node(("E",1000),Leaf,Leaf),
Leaf))
和“窃取”功能非常容易编写,假设你有通常的“折叠” 样板:
// have 'stealerName' take 'amount' from each of its children and
// add it to its own value
let Steal stealerName amount tree =
let Subtract amount = function
| Node((name,value),l,r) -> amount, Node((name,value-amount),l,r)
| Leaf -> 0, Leaf
tree |> XFoldTree
(fun (name,value) left right ->
if name = stealerName then
let leftAmt, newLeft = Subtract amount left
let rightAmt, newRight = Subtract amount right
XNode((name,value+leftAmt+rightAmt),newLeft,newRight)
else
XNode((name,value), left, right))
XLeaf
// examples
let dSteals10 = Steal "D" 10 origTree
let fSteals30 = Steal "F" 30 origTree
就是这样,你已经完成了,你已经编写了一个“更新”L和L级别的算法 仅仅通过创建核心逻辑,不可变树的L + 1。而不是解释 在这里,您应该阅读我的博客系列(至少在开头:部分one two three four)。
以下是所有代码(如上图所示):
// Tree boilerplate
// See http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!248.entry
type Tree<'T> =
| Node of 'T * Tree<'T> * Tree<'T>
| Leaf
let (===) x y = obj.ReferenceEquals(x,y)
let XFoldTree nodeF leafV tree =
let rec Loop t cont =
match t with
| Node(x,left,right) -> Loop left (fun lacc ->
Loop right (fun racc ->
cont (nodeF x lacc racc t)))
| Leaf -> cont (leafV t)
Loop tree (fun x -> x)
let XNode (x,l,r) (Node(xo,lo,ro) as orig) =
if xo = x && lo === l && ro === r then
orig
else
Node(x,l,r)
let XLeaf (Leaf as orig) =
orig
let FoldTree nodeF leafV tree =
XFoldTree (fun x l r _ -> nodeF x l r) (fun _ -> leafV) tree
// /////////////////////////////////////////
// stuff specific to this problem
let origTree = Node(("D",1000),
Node(("B",1000),
Node(("A",1000),Leaf,Leaf),
Node(("C",1000),Leaf,Leaf)),
Node(("F",1000),
Node(("E",1000),Leaf,Leaf),
Leaf))
// have 'stealerName' take 'amount' from each of its children and
// add it to its own value
let Steal stealerName amount tree =
let Subtract amount = function
| Node((name,value),l,r) -> amount, Node((name,value-amount),l,r)
| Leaf -> 0, Leaf
tree |> XFoldTree
(fun (name,value) left right ->
if name = stealerName then
let leftAmt, newLeft = Subtract amount left
let rightAmt, newRight = Subtract amount right
XNode((name,value+leftAmt+rightAmt),newLeft,newRight)
else
XNode((name,value), left, right))
XLeaf
let dSteals10 = Steal "D" 10 origTree
let fSteals30 = Steal "F" 30 origTree
// /////////////////////////////////////////
// once again,
// see http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!248.entry
// DiffTree: Tree<'T> * Tree<'T> -> Tree<'T * bool>
// return second tree with extra bool
// the bool signifies whether the Node "ReferenceEquals" the first tree
let rec DiffTree(tree,tree2) =
XFoldTree (fun x l r t t2 ->
let (Node(x2,l2,r2)) = t2
Node((x2,t===t2), l l2, r r2)) (fun _ _ -> Leaf) tree tree2
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media
open System.Windows.Shapes
// Handy functions to make multiple transforms be a more fluent interface
let IdentT() = new TransformGroup()
let AddT t (tg : TransformGroup) = tg.Children.Add(t); tg
let ScaleT x y (tg : TransformGroup) = tg.Children.Add(new ScaleTransform(x, y)); tg
let TranslateT x y (tg : TransformGroup) = tg.Children.Add(new TranslateTransform(x, y)); tg
// Draw: Canvas -> Tree<'T * bool> -> unit
let Draw (canvas : Canvas) tree =
// assumes canvas is normalized to 1.0 x 1.0
FoldTree (fun ((name,value),b) l r trans ->
// current node in top half, centered left-to-right
let tb = new TextBox(Width=100.0, Height=100.0, FontSize=30.0, Text=sprintf "%s:%d" name value,
// the tree is a "diff tree" where the bool represents
// "ReferenceEquals" differences, so color diffs Red
Foreground=(if b then Brushes.Black else Brushes.Red),
HorizontalContentAlignment=HorizontalAlignment.Center,
VerticalContentAlignment=VerticalAlignment.Center)
tb.RenderTransform <- IdentT() |> ScaleT 0.005 0.005 |> TranslateT 0.25 0.0 |> AddT trans
canvas.Children.Add(tb) |> ignore
// left child in bottom-left quadrant
l (IdentT() |> ScaleT 0.5 0.5 |> TranslateT 0.0 0.5 |> AddT trans)
// right child in bottom-right quadrant
r (IdentT() |> ScaleT 0.5 0.5 |> TranslateT 0.5 0.5 |> AddT trans)
) (fun _ -> ()) tree (IdentT())
let TreeToCanvas tree =
let canvas = new Canvas(Width=1.0, Height=1.0, Background = Brushes.Blue,
LayoutTransform=new ScaleTransform(400.0, 400.0))
Draw canvas tree
canvas
let TitledControl title control =
let grid = new Grid()
grid.ColumnDefinitions.Add(new ColumnDefinition())
grid.RowDefinitions.Add(new RowDefinition())
grid.RowDefinitions.Add(new RowDefinition())
let text = new TextBlock(Text = title, HorizontalAlignment = HorizontalAlignment.Center)
Grid.SetRow(text, 0)
Grid.SetColumn(text, 0)
grid.Children.Add(text) |> ignore
Grid.SetRow(control, 1)
Grid.SetColumn(control, 0)
grid.Children.Add(control) |> ignore
grid
let HorizontalGrid (controls:_[]) =
let grid = new Grid()
grid.RowDefinitions.Add(new RowDefinition())
for i in 0..controls.Length-1 do
let c = controls.[i]
grid.ColumnDefinitions.Add(new ColumnDefinition())
Grid.SetRow(c, 0)
Grid.SetColumn(c, i)
grid.Children.Add(c) |> ignore
grid
type MyWPFWindow(content, title) as this =
inherit Window()
do
this.Content <- content
this.Title <- title
this.SizeToContent <- SizeToContent.WidthAndHeight
[<System.STAThread()>]
do
let app = new Application()
let controls = [|
TitledControl "Original" (TreeToCanvas(DiffTree(origTree,origTree)))
TitledControl "D steals 10" (TreeToCanvas(DiffTree(origTree,dSteals10)))
TitledControl "F steals 30" (TreeToCanvas(DiffTree(origTree,fSteals30))) |]
app.Run(new MyWPFWindow(HorizontalGrid controls, "Fun with trees")) |> ignore
答案 1 :(得分:4)
我想如果你用“我必须修改树的节点”开始你的句子,修改同时陈述在两个不同的层面“那么你并没有真正以功能的方式解决你的问题。这就像用一种外语写一篇论文,首先用你的母语写作,然后试着翻译。不起作用。我知道这很疼,但在我看来,最好完全沉浸在自己身上。不要担心比较这些方法。
我发现学习“功能方式”的一种方法是查看(并实现自己!)一些functional pearls。他们基本上是文档超级功能优雅的程序,以解决各种问题。从较旧的开始,如果你没有得到它,不要害怕停止阅读并尝试另一个。稍后再回到它,重新获得热情和更多经验。它有助于:)
答案 2 :(得分:2)
你怎么看待这种“紧张” 我遇到了什么?我 解释“从不改变数据” 太严格了?你能建议我吗? 一些资源?
在我看来,如果你是第一次学习函数式编程,最好从零可变状态开始。否则,你最终只会以可变状态作为你的第一个手段,而你所有的F#代码都将是C#,语法略有不同。
关于数据结构,有些比其他结构更容易表达。您能否提供一下如何修改树的说明?
目前,我建议F# Wikibook's page on data structures查看数据结构是如何以函数式编写的。
我看过SICP,如何设计 节目并由C.发现了一篇论文。 Okasaki(“纯粹的功能数据 结构“)
我个人认为Okasaki's book比在线论文更具可读性。
答案 3 :(得分:2)
我必须修改树的节点。
不,你没有。那就是你的问题。
这让我付出了很多努力
这是典型的。学习使用不可变数据结构进行编程并不容易。对大多数初学者来说,起初看起来似乎不自然。这是非常困难的,因为HTDP和SICP没有给你很好的模型(见脚注)。
我认为你可以给我一些建议,当然我对“良好的函数式编程设计”的想法还没有很好地定义。
我们可以,但您必须告诉我们问题是什么。然后,这个论坛上的很多人都可以告诉你这是否是一种问题,其解决方案可以用一种明确的方式表达而不需要突变。大多数树问题都可以。但是根据你给我们的信息,我们无法告诉你。
我是否过于严格地解释“永不改变数据”?
不够严格,我会说。
请发一个问题,说明您要解决的问题。
脚注:HTDP和SICP都在Scheme中完成,缺少模式匹配。在这个设置中,理解树操作代码比使用F#提供的模式匹配更难 。就我而言,模式匹配是以纯粹的功能风格编写清晰代码的基本功能。对于资源,您可以考虑Graham Hutton关于Programming in Haskell的新书。
答案 4 :(得分:1)
查看Zipper数据结构。
答案 5 :(得分:0)
例如,我必须修改树的节点,同时修改两个不同级别(L和L + 1)的状态
为什么呢?在函数式语言中,您将创建一个新树。它可以重用不需要修改的子树,只需将它们插入新创建的根中即可。 “不要改变数据”并不意味着“试图在没有人注意的情况下改变数据,并且通过添加许多辅助方法,没有人实现这就是你正在做的事情。” p>
这意味着“不要改变你的数据。改为创建新的副本,用新的,正确的值初始化”。