%22%22%22Examples%20of%20converting%20and%20validating%20node%20values%20in%20Loman%20computations.%22%22%22%0A%0A%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.11%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%3D%3D0.23.13%22%2C%0A%23%20%20%20%20%20%22loman%22%2C%0A%23%20%5D%0A%23%0A%23%20%5Btool.uv.sources%5D%0A%23%20loman%20%3D%20%7B%20path%20%3D%20%22..%2F..%22%2C%20editable%20%3D%20true%20%7D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.23.13%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Converters%3A%20coercing%20and%20validating%20node%20values%0A%0A%20%20%20%20A%20node%20can%20be%20given%20a%20%60converter%60%3A%20a%20callable%20Loman%20applies%20to%20a%20value%20on%20its%20way%0A%20%20%20%20**into**%20the%20node.%20The%20node%20stores%20what%20the%20converter%20returns%2C%20not%20what%20was%20supplied.%0A%0A%20%20%20%20That%20one%20hook%20covers%20two%20jobs%3A%0A%0A%20%20%20%20-%20**Coercion**%20%E2%80%94%20force%20a%20value%20into%20the%20type%20the%20rest%20of%20the%20graph%20expects%0A%20%20%20%20%20%20(%60converter%3Dfloat%60).%0A%20%20%20%20-%20**Validation**%20%E2%80%94%20check%20a%20value%20and%20raise%20if%20it%20is%20unacceptable.%20Loman%20has%20no%0A%20%20%20%20%20%20separate%20validator%20hook%3B%20a%20converter%20that%20raises%20is%20how%20you%20get%20one.%0A%0A%20%20%20%20Both%20push%20the%20check%20to%20the%20graph's%20boundary%2C%20so%20downstream%20nodes%20can%20trust%20their%0A%20%20%20%20inputs%20instead%20of%20re-checking%20them.%0A%0A%20%20%20%20This%20notebook%20has%20a%20few%20interactive%20controls.%20Run%20it%20top%20to%20bottom%2C%20then%20edit%20the%0A%20%20%20%20inputs%20to%20see%20how%20each%20node%20reacts.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20from%20loman%20import%20Computation%0A%0A%20%20%20%20def%20node_report(comp%2C%20*names)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Summarise%20state%2C%20stored%20value%20and%20type%20for%20the%20given%20nodes.%22%22%22%0A%20%20%20%20%20%20%20%20rows%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20name%20in%20names%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20state%20%3D%20comp.s%5Bname%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20value%20%3D%20comp.v%5Bname%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20hasattr(value%2C%20%22exception%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shown%2C%20type_name%20%3D%20f%22%7Btype(value.exception).__name__%7D%3A%20%7Bvalue.exception%7D%22%2C%20%22%E2%80%94%22%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shown%2C%20type_name%20%3D%20repr(value)%2C%20type(value).__name__%0A%20%20%20%20%20%20%20%20%20%20%20%20rows.append(%7B%22node%22%3A%20str(name)%2C%20%22state%22%3A%20str(state)%2C%20%22value%22%3A%20shown%2C%20%22type%22%3A%20type_name%7D)%0A%20%20%20%20%20%20%20%20return%20rows%0A%0A%20%20%20%20return%20Computation%2C%20mo%2C%20node_report%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%201.%20Coercing%20a%20value%20at%20the%20boundary%0A%0A%20%20%20%20Input%20often%20arrives%20as%20text%20%E2%80%94%20from%20a%20CSV%2C%20a%20form%2C%20a%20config%20file.%20Attaching%0A%20%20%20%20%60converter%3Dfloat%60%20to%20the%20input%20node%20means%20the%20conversion%20happens%20once%2C%20at%20the%20point%0A%20%20%20%20of%20entry%2C%20rather%20than%20in%20every%20calculation%20that%20reads%20it.%0A%0A%20%20%20%20Type%20a%20value%20below.%20It%20is%20inserted%20as%20a%20**string**%2C%20but%20the%20node%20stores%20a%20%60float%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20raw_input_box%20%3D%20mo.ui.text(value%3D%223.5%22%2C%20label%3D%22Raw%20text%20value%20to%20insert%3A%22)%0A%20%20%20%20raw_input_box%0A%20%20%20%20return%20(raw_input_box%2C)%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20node_report%2C%20raw_input_box)%3A%0A%20%20%20%20coerce%20%3D%20Computation()%0A%20%20%20%20coerce.add_node(%22quantity%22%2C%20converter%3Dfloat)%0A%20%20%20%20coerce.add_node(%22doubled%22%2C%20lambda%20quantity%3A%20quantity%20*%202)%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20coerce.insert(%22quantity%22%2C%20raw_input_box.value)%0A%20%20%20%20%20%20%20%20coerce.compute_all()%0A%20%20%20%20%20%20%20%20coerce_outcome%20%3D%20%22inserted%22%0A%20%20%20%20except%20ValueError%20as%20exc%3A%0A%20%20%20%20%20%20%20%20coerce_outcome%20%3D%20f%22insert%20raised%20%7Btype(exc).__name__%7D%3A%20%7Bexc%7D%22%0A%0A%20%20%20%20%7B%22outcome%22%3A%20coerce_outcome%2C%20%22nodes%22%3A%20node_report(coerce%2C%20%22quantity%22%2C%20%22doubled%22)%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20string%20never%20reaches%20the%20node%2C%20so%20%60doubled%60%20multiplies%20a%20number%20rather%20than%0A%20%20%20%20accidentally%20repeating%20a%20string.%20Try%20entering%20%60abc%60%3A%20the%20conversion%20fails%2C%20the%20insert%0A%20%20%20%20raises%2C%20and%20%60quantity%60%20is%20left%20in%20%60ERROR%60%20%E2%80%94%20the%20bad%20value%20is%20not%20stored.%0A%0A%20%20%20%20%23%23%202.%20Converters%20apply%20to%20computed%20values%20too%0A%0A%20%20%20%20This%20is%20the%20part%20that%20surprises%20people.%20A%20converter%20belongs%20to%20the%20**node**%2C%20not%20to%0A%20%20%20%20the%20insert%20path%2C%20so%20it%20also%20runs%20on%20the%20value%20the%20node%20calculates%20for%20itself.%0A%0A%20%20%20%20Below%2C%20the%20lambda%20returns%20an%20%60int%60.%20The%20node%20holds%20a%20%60float%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20node_report)%3A%0A%20%20%20%20computed%20%3D%20Computation()%0A%20%20%20%20computed.add_node(%22a%22%2C%20value%3D1)%0A%20%20%20%20computed.add_node(%22b%22%2C%20lambda%20a%3A%20a%20%2B%201%2C%20converter%3Dfloat)%0A%20%20%20%20computed.compute_all()%0A%20%20%20%20node_report(computed%2C%20%22a%22%2C%20%22b%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60b%60%20computed%20%602%60%20and%20stored%20%602.0%60.%20This%20makes%20a%20converter%20a%20useful%20guarantee%20about%20a%0A%20%20%20%20node's%20contents%20no%20matter%20how%20the%20value%20got%20there%20%E2%80%94%20which%20is%20exactly%20what%20you%20want%0A%20%20%20%20when%20the%20node%20is%20a%20published%20output%20that%20other%20systems%20read.%0A%0A%20%20%20%20%23%23%203.%20A%20converter%20that%20raises%20is%20a%20validator%0A%0A%20%20%20%20A%20converter%20is%20free%20to%20inspect%20its%20argument%20and%20refuse%20it.%20Raising%20leaves%20the%20node%20in%0A%20%20%20%20%60ERROR%60%20state%20and%20the%20value%20unstored.%0A%0A%20%20%20%20The%20important%20discipline%3A%20**return%20the%20value%20when%20it%20passes**.%20A%20validator%20that%0A%20%20%20%20forgets%20to%20return%20silently%20replaces%20the%20node's%20value%20with%20%60None%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20size_slider%20%3D%20mo.ui.slider(-5%2C%2010%2C%20value%3D4%2C%20label%3D%22Value%20to%20insert%20into%20%60size%60%3A%22)%0A%20%20%20%20size_slider%0A%20%20%20%20return%20(size_slider%2C)%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20node_report%2C%20size_slider)%3A%0A%20%20%20%20def%20positive(x)%3A%0A%20%20%20%20%20%20%20%20if%20x%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20msg%20%3D%20f%22must%20be%20positive%2C%20got%20%7Bx%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(msg)%0A%20%20%20%20%20%20%20%20return%20x%20%20%23%20a%20validator%20must%20return%20the%20value%20it%20accepts%0A%0A%20%20%20%20validated%20%3D%20Computation()%0A%20%20%20%20validated.add_node(%22size%22%2C%20converter%3Dpositive)%0A%20%20%20%20validated.add_node(%22area%22%2C%20lambda%20size%3A%20size**2)%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20validated.insert(%22size%22%2C%20size_slider.value)%0A%20%20%20%20%20%20%20%20validated.compute_all()%0A%20%20%20%20%20%20%20%20validate_outcome%20%3D%20%22accepted%22%0A%20%20%20%20except%20ValueError%20as%20exc%3A%0A%20%20%20%20%20%20%20%20validate_outcome%20%3D%20f%22rejected%20%E2%80%94%20insert%20raised%20%7Btype(exc).__name__%7D%3A%20%7Bexc%7D%22%0A%0A%20%20%20%20%7B%22outcome%22%3A%20validate_outcome%2C%20%22nodes%22%3A%20node_report(validated%2C%20%22size%22%2C%20%22area%22)%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Drag%20the%20slider%20to%20zero%20or%20below.%20The%20insert%20is%20rejected%2C%20%60size%60%20goes%20to%20%60ERROR%60%2C%20and%0A%20%20%20%20%60area%60%20never%20computes%20off%20a%20nonsensical%20input%20%E2%80%94%20the%20failure%20surfaces%20at%20the%20boundary%0A%20%20%20%20instead%20of%20several%20nodes%20downstream.%0A%0A%20%20%20%20%23%23%204.%20Coercing%20and%20validating%20together%0A%0A%20%20%20%20In%20practice%20the%20two%20jobs%20combine%3A%20coerce%20the%20value%20into%20the%20right%20type%2C%20then%20assert%0A%20%20%20%20the%20result%20is%20usable.%20A%20trade%20ticket%20arriving%20as%20text%20is%20a%20good%20example%20%E2%80%94%20each%20field%0A%20%20%20%20is%20parsed%20and%20checked%20as%20it%20enters%20the%20graph.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20ticket_notional%20%3D%20mo.ui.text(value%3D%221000000%22%2C%20label%3D%22notional%22)%0A%20%20%20%20ticket_currency%20%3D%20mo.ui.text(value%3D%22usd%22%2C%20label%3D%22currency%22)%0A%20%20%20%20mo.vstack(%5Bticket_notional%2C%20ticket_currency%5D)%0A%20%20%20%20return%20ticket_currency%2C%20ticket_notional%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20node_report%2C%20ticket_currency%2C%20ticket_notional)%3A%0A%20%20%20%20known_currencies%20%3D%20%7B%22USD%22%2C%20%22EUR%22%2C%20%22GBP%22%2C%20%22JPY%22%7D%0A%0A%20%20%20%20def%20positive_amount(x)%3A%0A%20%20%20%20%20%20%20%20amount%20%3D%20float(x)%0A%20%20%20%20%20%20%20%20if%20amount%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20msg%20%3D%20f%22notional%20must%20be%20positive%2C%20got%20%7Bamount%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(msg)%0A%20%20%20%20%20%20%20%20return%20amount%0A%0A%20%20%20%20def%20currency_code(x)%3A%0A%20%20%20%20%20%20%20%20code%20%3D%20str(x).strip().upper()%0A%20%20%20%20%20%20%20%20if%20code%20not%20in%20known_currencies%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20msg%20%3D%20f%22unknown%20currency%20%7Bcode!r%7D%2C%20expected%20one%20of%20%7Bsorted(known_currencies)%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(msg)%0A%20%20%20%20%20%20%20%20return%20code%0A%0A%20%20%20%20ticket%20%3D%20Computation()%0A%20%20%20%20ticket.add_node(%22notional%22%2C%20converter%3Dpositive_amount)%0A%20%20%20%20ticket.add_node(%22currency%22%2C%20converter%3Dcurrency_code)%0A%20%20%20%20ticket.add_node(%22description%22%2C%20lambda%20notional%2C%20currency%3A%20f%22%7Bnotional%3A%2C.2f%7D%20%7Bcurrency%7D%22)%0A%0A%20%20%20%20ticket_errors%20%3D%20%5B%5D%0A%20%20%20%20for%20_field%2C%20_element%20in%20((%22notional%22%2C%20ticket_notional)%2C%20(%22currency%22%2C%20ticket_currency))%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ticket.insert(_field%2C%20_element.value)%0A%20%20%20%20%20%20%20%20except%20ValueError%20as%20exc%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ticket_errors.append(f%22%7B_field%7D%3A%20%7Bexc%7D%22)%0A%20%20%20%20ticket.compute_all()%0A%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22rejected%22%3A%20ticket_errors%20or%20%22nothing%20rejected%22%2C%0A%20%20%20%20%20%20%20%20%22nodes%22%3A%20node_report(ticket%2C%20%22notional%22%2C%20%22currency%22%2C%20%22description%22)%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Note%20that%20%60currency%60%20is%20normalized%20as%20well%20as%20checked%3A%20%60usd%60%20is%20stored%20as%20%60USD%60%2C%20so%0A%20%20%20%20downstream%20nodes%20can%20compare%20against%20uppercase%20codes%20without%20defensive%20%60.upper()%60%0A%20%20%20%20calls.%20Try%20%60chf%60%20to%20see%20a%20rejection%2C%20and%20note%20that%20%60description%60%20is%20then%20unable%20to%0A%20%20%20%20compute%20while%20the%20other%20field%20remains%20fine.%0A%0A%20%20%20%20%23%23%205.%20How%20failures%20reach%20your%20code%0A%0A%20%20%20%20A%20failed%20conversion%20always%20leaves%20the%20node%20in%20%60ERROR%60%2C%20with%20the%20exception%20available%20at%0A%20%20%20%20%60comp.v%5Bnode%5D.exception%60.%20Whether%20it%20*also*%20propagates%20to%20the%20caller%20depends%20on%20how%0A%20%20%20%20the%20value%20arrived%3A%0A%0A%20%20%20%20%7C%20Value%20arrives%20via%20%7C%20Node%20state%20%7C%20Exception%20raised%20to%20caller%20%7C%0A%20%20%20%20%7C%20---%20%7C%20---%20%7C%20---%20%7C%0A%20%20%20%20%7C%20%60add_node(value%3D...)%60%20%7C%20%60ERROR%60%20%7C%20Yes%20%7C%0A%20%20%20%20%7C%20%60insert%60%20%2F%20%60insert_many%60%20%7C%20%60ERROR%60%20%7C%20Yes%20%7C%0A%20%20%20%20%7C%20A%20calculation%20(%60compute%60%2C%20%60compute_all%60)%20%7C%20%60ERROR%60%20%7C%20No%20%7C%0A%0A%20%20%20%20Insertion%20raises%20because%20supplying%20a%20value%20is%20something%20your%20code%20just%20did%20and%20can%0A%20%20%20%20handle%20on%20the%20spot.%20A%20conversion%20failure%20during%20computation%20is%20treated%20like%20any%20other%0A%20%20%20%20node%20failure%3A%20the%20node%20is%20marked%20%60ERROR%60%2C%20its%20descendants%20cannot%20proceed%2C%20and%20the%20run%0A%20%20%20%20continues%20so%20unrelated%20branches%20still%20make%20progress.%0A%0A%20%20%20%20Below%2C%20the%20same%20%60positive%60%20validator%20fails%20on%20a%20*computed*%20value.%20%60compute_all()%60%0A%20%20%20%20returns%20normally%2C%20and%20the%20healthy%20branch%20still%20finishes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20node_report)%3A%0A%20%20%20%20def%20positive_check(x)%3A%0A%20%20%20%20%20%20%20%20if%20x%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20msg%20%3D%20f%22must%20be%20positive%2C%20got%20%7Bx%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(msg)%0A%20%20%20%20%20%20%20%20return%20x%0A%0A%20%20%20%20compute_fail%20%3D%20Computation()%0A%20%20%20%20compute_fail.add_node(%22a%22%2C%20value%3D1)%0A%20%20%20%20compute_fail.add_node(%22b%22%2C%20lambda%20a%3A%20a%20-%2010%2C%20converter%3Dpositive_check)%0A%20%20%20%20compute_fail.add_node(%22c%22%2C%20lambda%20b%3A%20b%20*%202)%0A%20%20%20%20compute_fail.add_node(%22unrelated%22%2C%20lambda%20a%3A%20a%20*%20100)%0A%20%20%20%20compute_fail.compute_all()%20%20%23%20note%3A%20does%20not%20raise%0A%20%20%20%20node_report(compute_fail%2C%20%22a%22%2C%20%22b%22%2C%20%22c%22%2C%20%22unrelated%22)%0A%20%20%20%20return%20(compute_fail%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60b%60%20is%20%60ERROR%60%2C%20%60c%60%20is%20left%20%60STALE%60%20behind%20it%2C%20and%20%60unrelated%60%20is%20up%20to%20date.%20A%20node%0A%20%20%20%20left%20in%20%60ERROR%60%20by%20a%20failed%20conversion%20is%20reported%20by%20%60validate()%60%20exactly%20like%20any%0A%20%20%20%20other%20failed%20node%2C%20so%20the%20usual%20diagnostics%20apply.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(compute_fail)%3A%0A%20%20%20%20compute_fail.validate().to_df()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%206.%20Three%20things%20worth%20knowing%0A%0A%20%20%20%20**Redefining%20a%20node%20drops%20its%20converter.**%20%60add_node%60%20replaces%20the%20whole%20node%0A%20%20%20%20definition%2C%20so%20calling%20it%20again%20without%20%60converter%3D%60%20leaves%20the%20node%20unconverted.%20Pass%0A%20%20%20%20the%20converter%20every%20time%20you%20redefine%20the%20node.%0A%0A%20%20%20%20**Converters%20survive%20saving%2C%20but%20must%20be%20importable.**%20%60write_json%60%2F%60read_json%60%20and%0A%20%20%20%20%60save%60%2F%60load%60%20store%20a%20converter%20the%20way%20they%20store%20a%20node's%20function%3A%20by%20reference.%20A%0A%20%20%20%20module-level%20function%20or%20a%20builtin%20such%20as%20%60float%60%20comes%20back%20intact%2C%20so%20a%20reloaded%0A%20%20%20%20graph%20still%20coerces%20and%20still%20validates.%20A%20%60lambda%60%20has%20no%20importable%20path%20and%20raises%0A%20%20%20%20%60SerializationError%60%20naming%20the%20node%2C%20so%20define%20converters%20at%20module%20level%20rather%20than%0A%20%20%20%20inline.%0A%0A%20%20%20%20**%60add_block%60%20keeps%20converters.**%20A%20validated%20block%20template%20stays%20validated%20wherever%0A%20%20%20%20it%20is%20used.%0A%0A%20%20%20%20The%20cell%20below%20demonstrates%20all%20three.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation)%3A%0A%20%20%20%20import%20io%0A%0A%20%20%20%20%23%20Redefinition%20drops%20the%20converter%0A%20%20%20%20redefine%20%3D%20Computation()%0A%20%20%20%20redefine.add_node(%22x%22%2C%20converter%3Dfloat)%0A%20%20%20%20redefine.add_node(%22x%22)%20%20%23%20no%20converter%3D%20this%20time%0A%20%20%20%20redefine.insert(%22x%22%2C%205)%0A%0A%20%20%20%20%23%20An%20importable%20converter%20survives%20a%20JSON%20round%20trip%0A%20%20%20%20saved%20%3D%20Computation()%0A%20%20%20%20saved.add_node(%22y%22%2C%20value%3D1%2C%20converter%3Dfloat)%0A%20%20%20%20_buffer%20%3D%20io.StringIO()%0A%20%20%20%20saved.write_json(_buffer)%0A%20%20%20%20_buffer.seek(0)%0A%20%20%20%20reloaded%20%3D%20Computation.read_json(_buffer)%0A%20%20%20%20reloaded.insert(%22y%22%2C%207%2C%20force%3DTrue)%0A%0A%20%20%20%20%23%20A%20lambda%20converter%20has%20no%20importable%20path%0A%20%20%20%20inline%20%3D%20Computation()%0A%20%20%20%20inline.add_node(%22w%22%2C%20value%3D1%2C%20converter%3Dlambda%20v%3A%20float(v))%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20inline.write_json(io.StringIO())%0A%20%20%20%20%20%20%20%20lambda_outcome%20%3D%20%22saved%22%0A%20%20%20%20except%20Exception%20as%20exc%3A%0A%20%20%20%20%20%20%20%20lambda_outcome%20%3D%20type(exc).__name__%0A%0A%20%20%20%20%23%20add_block%20keeps%20it%0A%20%20%20%20template%20%3D%20Computation()%0A%20%20%20%20template.add_node(%22z%22%2C%20converter%3Dfloat)%0A%20%20%20%20host%20%3D%20Computation()%0A%20%20%20%20host.add_block(%22blk%22%2C%20template)%0A%20%20%20%20host.insert(%22blk%2Fz%22%2C%202)%0A%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22after%20redefining%20without%20converter%3D%22%3A%20type(redefine.v%5B%22x%22%5D).__name__%2C%0A%20%20%20%20%20%20%20%20%22after%20write_json%20%2F%20read_json%22%3A%20type(reloaded.v%5B%22y%22%5D).__name__%2C%0A%20%20%20%20%20%20%20%20%22saving%20a%20lambda%20converter%22%3A%20lambda_outcome%2C%0A%20%20%20%20%20%20%20%20%22inside%20a%20block%20added%20with%20add_block%22%3A%20type(host.v%5B%22blk%2Fz%22%5D).__name__%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Only%20the%20first%20is%20%60int%60%20%E2%80%94%20redefining%20the%20node%20dropped%20its%20converter.%20The%20round-tripped%0A%20%20%20%20node%20is%20still%20a%20%60float%60%2C%20the%20lambda%20is%20refused%20with%20%60SerializationError%60%2C%20and%20the%20block%0A%20%20%20%20carries%20its%20converter%20along.%0A%0A%20%20%20%20%23%23%20Summary%0A%0A%20%20%20%20%7C%20Goal%20%7C%20Converter%20%7C%0A%20%20%20%20%7C%20---%20%7C%20---%20%7C%0A%20%20%20%20%7C%20Accept%20text%20input%20as%20a%20number%20%7C%20%60converter%3Dfloat%60%20%7C%0A%20%20%20%20%7C%20Normalize%20a%20code%20or%20label%20%7C%20%60lambda%20x%3A%20str(x).strip().upper()%60%20%7C%0A%20%20%20%20%7C%20Reject%20an%20out-of-range%20value%20%7C%20raise%20in%20the%20converter%2C%20return%20%60x%60%20when%20it%20passes%20%7C%0A%20%20%20%20%7C%20Coerce%20then%20check%20%7C%20convert%20first%2C%20validate%20the%20result%2C%20return%20it%20%7C%0A%20%20%20%20%7C%20Guarantee%20a%20computed%20output's%20type%20%7C%20attach%20the%20converter%20to%20the%20calculation%20node%20%7C%0A%0A%20%20%20%20Keep%20converters%20cheap%20and%20free%20of%20side%20effects%3A%20one%20runs%20on%20every%20set%20of%20the%20node%2C%0A%20%20%20%20including%20repeat%20inserts%2C%20and%20its%20return%20value%20is%20what%20the%20whole%20graph%20downstream%0A%20%20%20%20sees.%0A%0A%20%20%20%20See%20also%20the%20%5BConverters%20and%20Validation%5D(..%2Fuser%2Ffeatures%2Fcreating%2Fconverters_and_validation%2F)%0A%20%20%20%20page%20in%20the%20user%20guide.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
e04615e80bf1f14f0a052ae8b70ca945