2016-10-05 7 views
6

私はテキストノードのようなツリー構造を持っています。これは、別のテキストノードを子として持つかもしれません。その中の1つの値を更新する必要があります。そのツリーのどこかにあるテキストノードを更新する最も簡単な方法は何ですか(またはそのツリーにはまったくありません)。再帰型の値を更新する - elm lang

不変でない言語では、単にその項目の値を変更するだけですが、それだけですが、Elmのような不変言語ではかなり難しいです。

type alias Item = 
    { id: String 
    , text: String 
    , children: ChildItems 
    } 

type ChildItems = ChildItems (List Item) 

type alias Model = 
    { rootItem: Item 
    } 

updateItem: Item -> Item -> Item 
updateItem: rootItem item = 
    -- TODO 

... 

update model = 
    case msg of 
    UpdateItem item updatedText -> 
     let 
     updatedItem = { item | text = updatedText } 
     in 
     ({ model | rootItem = (updateItem model.rootItem updatedItem) }, Cmd.none) 

これは私が

updateItem: Item.Item -> Item.Item -> Item.Item 
updateItem rootItem updatedItem = 
    if rootItem.id == updatedItem.id then 
    updatedItem 
    else 
    case rootItem.children of 
     Item.ChildItem [] -> 
     rootItem 

     Item.ChildItem children -> 
     let 
      updatedChildren = 
      case children of 
       [] -> 
       [] 

       children -> 
       List.map (\item -> 
        updateItem rootItem item) children 
     in 
      { rootItem | children = Item.ChildItem updatedChildren } 

思い付いたものですが、私はあなたがrootItemの代わりに戻ってきているので、あなたは、スタックオーバーフローを取得している理由があるMaximum call stack size exceededエラーに

答えて

15

を取得していますItem.ChildItems []の場合は[]です。

一般的なパターンがいくつかあるので、コードを少し修正します。まず第一に、それは、もののいずれかのタイプに合うことができるようにのが根底にあるツリーっぽい構造をとり、より汎用的なことをしてみましょうだけでなくItem:私たちは常にルートノードを持つ構造を与える

type Node a 
    = Node a (List (Node a)) 

任意の数の子供を持つことができ、それぞれの子供は任意の数の子供を持つことができる。

あなたが行っていたアルゴリズムについて考えると、おなじみのパターンを推定することができます。複数のアイテムを持つ構造があり、各アイテムを訪問し、オプションでそれを変更するアルゴリズムが必要です。それはList.mapのように聞こえます。

map : (a -> b) -> Node a -> Node b 
map f (Node item children) = 
    Node (f item) (List.map (map f) children) 

:それは私たちの一般機能map呼び出すために良いアイデアだと、このような一般的なイディオムです(サイドノート:私達はちょうどfunctorsにつまずいた!)

私はアイデアを撮影したので、今、私たちはを持っていることを

type alias Item = 
    { id: String 
    , text: String 
    } 

:子供のとNodeタイプにそれを置き、我々はそうのようなエイリアスItemを変更する必要がありますの場合、idが特定の値と一致すると更新できる関数を持つと便利です。

updateByID : String -> (Item -> Item) -> Item -> Item 
updateByID id f item = 
    if item.id == id then 
    f item 
    else 
    item 

が上の更新を実行する:将来的には、あなたが実行したい複数の更新機能を持っているかもしれないので、それはあなたが実際に実行したい機能とは別のルックアップとIDのマッチング部分を維持するのが最善ですツリー内の任意の場所にIDと一致するアイテムを追加することができます。

map (updateByID "someID" (\x -> { x | text = "Woohoo!" })) rootNode 
+0

優秀な回答!あなたは私の一日を作った:) –

+0

このパターンを使ってアイテムのネストされたリストを更新するには? 私が打つ問題は、レコード更新式が、表示される関数の戻り式になるように設計されていることです。新しいItemを作成するように設計された関数は、戻り値の式はアイテムのものではなく新しいアイテムにする必要があります。私が更新する必要がある親の子供のリスト。 再帰型(別名「ツリー」)構造を扱う方法に関する聖句はありますか?アイテムの型名をエイリアスにし、コレクションを型にする方法を見てきました。あなたは別のプロトコルを提供します。 –

+0

@RichardHavenは、map関数によって処理されます。与えられたノードからアイテムを構成し、関数によって変更された可能性のあるノードと、それが与えられたノードの子を作成します関数によって変更される可能性があります。最終的にupdateByIdをマップに渡すと、変更された1つのアイテムを除き、同じ部分を使用してノードのツリーが再帰的に構築されます。 –