Even with the technical differences between Elm and UI.Next which István mentioned, it is still possible to write code in WebSharper with a similar style as Elm. As I posted on your reddit thread, I have ported the first example from the Elm architecture tutorial to WebSharper, and I think it shouldn't be too hard to do the same with the other examples.
You can use a more complex model type in just the same way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
type Model = { first: int; second: int } type Action = IncrementFirst | DecrementFirst | IncrementSecond | DecrementSecond let Update (action: Action) (model: Model) : Model = match action with | IncrementFirst -> { model with first = model.first + 1 } | DecrementFirst -> { model with first = model.first - 1 } | IncrementSecond -> { model with first = model.second + 1 } | DecrementSecond -> { model with first = model.second - 1 } let Render (send: Action -> unit) (model: View<Model>) = div [ h1 [text "First:"] buttonAttr [on.click (fun _ _ -> send DecrementFirst)] [text "-"] divAttr [countStyle] [textView (model.Map (fun m -> string m.first))] buttonAttr [on.click (fun _ _ -> send IncrementFirst)] [text "+"] h1 [text "Second:"] buttonAttr [on.click (fun _ _ -> send DecrementSecond)] [text "-"] divAttr [countStyle] [textView (model.Map (fun m -> string m.second))] buttonAttr [on.click (fun _ _ -> send IncrementSecond)] [text "+"] ] :> Doc
However things get more complicated if you want to, for example, directly access the model with an input field.
Actually, the more I think about it, the more I'm convinced the Action
-based architecture is actually not a very good fit for UI.Next, in large part because of this. Instead, you can still have a functional architecture with a single model using IRef<'T>
. It's an interface that is implemented by Var<'T>
, but can also be created to lens into a field of another IRef<'T>
. It basically allows you to functionally define a bidirectional mapping between an input field and a part of your model (eg. a record field). Here I posted an IRef
-based version of the two-field case with editable input fields.
To access an input field you'd probably add another action called "Edit". I've made a demo here. I don't have much experience with lenses (next on my to do list), but is defining additional lenses and using IRef<'T> simpler than sending a new action on each input change?
Well the difference is that in UI.Next there is a family of functions Doc.Input
(the one I used here is Doc.IntInputUnchecked
) that take as argument an IRef<T>
, so you can just do Doc.Input [] someLensedRef
and get a two-way binding, unlike Elm where (according to your code above) it seems that you need to bind the "input" event by hand.
Thanks for posting this - I'm currently evaluating both Elm and Websharper and this is a very interesting and helpful example. One thing I don't yet understand is whether this approach does the same kind of dom manipulation optimization as elm if you have a single var with an all encompassing model and rerender it every time something changes.
My mental model of what happens with elm is that there is some clever dom comparison stuff which compares the old and new dom models and works out how to update the browser efficiently, so if all you've done is painted a single character, that's all that gets rerendered, even though the entire model object has been replaced by a different immutable one.
Conversely, my mental model of what happens with websharper UI.Next is that you need to have explicitly defined the dependencies using Vars and Views etc, and any change, however small, to any part of a Var will recalculate and rerender all dom elements which have Views into it, so that with only one Var, the entire screen would be rerendered for every action.
Is this right, or does Websharper do some clever optimization internally to avoid replacing most of the dom with identical new elements?
The reason I'm asking is that so far, I find the logical clarity of the Elm approach very nice now I have my head round it, but there's currently too much missing from the language to give me a realistic starting point for any real projects. Websharper and the F# infrastructure has all I need in that respect, but I don't want to have to go back to the approach in which I need to care about which bits of my view depend in which way on which bits of my model. If all I have to do is ensure that I know how to render a snapshot of my model correctly, and leave the framework to optimize, my code is easier to understand and more maintainable.
So the possibility that I can simply have a single Var containing my entire model, render it more or less without worrying about dependencies really appeals, but not if performance will go down by an order of magnitude....
You are right that if you have your whole mutable state in a single Var
and render
your document from that then the whole thing will be rerendered each time the state changes. As far as I know, UI.Next will not do any clever optimizations to diff the current state of the DOM with the one being rendered and only patch the relevant parts. However, I'd argue that, in case of any non trivial application, having a single Var
to represent the whole state is pratical. This defeats the whole purpose of UI.Next. Explicit dependencies on different Var
s and View
s make for better application design in my opinion. Since functional programming is so good at composition, what you usually end up with is having a couple of local Var
s and the logic to render them and you compose those little pieces on a higher level to build your application. To my understanding, this is very similar to how React manages application structure. You define little reactive components with minimal mutable state where it's easy to reason about state changes and side effects, and then you build your application up from those little pieces. I can't see a good reason to have the whole state in a single Var
.
For reference, I have worked with some applications built with UI.Next (nothing huge but more than just login forms), and I never had any performance problems where I had to think hard about the dependencies between the components and which View
depends on which Var
. Writing modular code usually leads up to having a good application structure which coincides with proper reactive state handling. In my mind, having a single Var
that contains everything is like having global mutable state.
Thanks for that clarification. It's more or less what I thought. I don't have a serious problem with adopting Views and Vars within my UI layer, provided I can avoid writing huge amounts of boilerplate. I'd like to understand my options a bit better here, but I think it would be off topic, so I'll describe my issue(s) in another post.
I replied on reddit but for completeness here's what I wrote:
Thank you for your demo, it clarified a lot. I think the main thing that's missing is signals. As described here:
https://github.com/intellifactory/websharper.ui.next/blob/master/docs/EventStreams.md
the recommended way in websharper is callbacks that set mutable variables.
I'm not really familiar with Elm but if the typesystem is like Haskell's then probably all the effects are captured in
monads. This wouldn't be very practical to do in F# since the type system is not powerful enough (no higher kinded types for example) and you have no type classes to do things the same way. You couldn't use monad transformers to combine effects and it would become cumbersome to switch between contexts. We basically achive the same thing with Var
s only that the side effect is not explicit in the type. This gives us conveniance of use and room to optimize (computation expressions, which are similar to what monads do in Haskell, get compiled to chained function calls which can be really slow compared to imperative code).
Also you can find a short notice of some design decisions why we didn't go with Elm style signals and streams in the GitHub docs.
Elm is actually a more restrictive language than F#, because not only does it not have type classes but it also lacks computation expressions (monads). The only extra feature I've seen is extendable records (which is doesn't really rely on to work).
I read that link but don't understand this part (under Elm): The tradeoff is that Elm signals do not allow dynamic composition, there is no Signal (Signal a) -> Signal a combinator, whereas this is available in our framework as View.Join. I'd be greatful if you (or someone else) could elaborate, thanks!
A function that is as powerful as join : Signal (Signal a) -> Signal a
but easier to understand
(IMO) is bind : (a -> Signal b) -> Signal a -> Signal b
(or in terms of UI.Next views:
Bind : ('a -> View<'b>) -> View<'a> -> View<'b>)
. This is a function that basically allows you to select a different output signal depending on the value of an input signal. Here is an example where this is useful:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
type Contact = Email of string | PhoneNumber of string let isEmail = Var.Create true let emailInput = Var.Create "" let phoneInput = Var.Create "" let outputValue : View<Contact> = isEmail |> View.Bind (function | true -> View.Map Email emailInput.View | false -> View.Map PhoneNumber phoneInput.View) let rendered : Elt = div [ Doc.Select [] (function true -> "Email" | false -> "Phone number") [true; false] isEmail isEmail.Doc(function | true -> Doc.Input [] emailInput | false -> Doc.Input [] phoneInput) outputValue.Doc(function | Email x -> text ("Your email address is " + x) | PhoneNumber x -> text ("Your phone number is " + x)) ]
Here, depending on the current value of isEmail
, the view outputValue
creates its output from a different input view. This is something that Elm doesn't allow, but UI.Next does.
Topic tags
- f# × 3705
- websharper × 1897
- compiler × 286
- functional × 201
- ui next × 139
- c# × 121
- classes × 97
- web × 97
- .net × 84
- book × 84
- async × 76
- ui.next × 67
- bug × 54
- core × 49
- website × 49
- server × 45
- parallel × 43
- ui × 43
- enhancement × 41
- parsing × 41
- testing × 41
- trywebsharper × 41
- typescript × 37
- html × 35
- javascript × 35
- owin × 35
- asynchronous × 30
- monad × 28
- ocaml × 28
- tutorial × 27
- warp × 27
- haskell × 26
- sitelet × 25
- linq × 22
- workflows × 22
- wpf × 20
- fpish × 19
- introduction × 19
- silverlight × 19
- sitelets × 19
- monodevelop × 17
- rpc × 17
- suave × 17
- piglets × 16
- collections × 15
- feature request × 15
- jquery × 15
- templates × 15
- getting started × 14
- pipeline × 14
- kendoui × 13
- reactive × 12
- 4.1.0.171 × 11
- monads × 11
- opinion × 10
- 4.0.190.100-rc × 9
- deployment × 9
- fixed × 9
- formlets × 9
- in × 9
- json × 9
- plugin × 9
- proposal × 9
- scheme × 9
- solid × 9
- basics × 8
- concurrent × 8
- highcharts × 8
- how-to × 8
- python × 8
- 4.1.1.175 × 7
- complexity × 7
- documentation × 7
- visual studio × 7
- 4.1.2.178 × 6
- lisp × 6
- real-world × 6
- released in 4.0.192.103-rc × 6
- remoting × 6
- resources × 6
- scala × 6
- websharper ui.next × 6
- workshop × 6
- xaml × 6
- 4.0.193.110 × 5
- 4.2.3.236 × 5
- aspnetmvc × 5
- authentication × 5
- azure × 5
- bootstrap × 5
- conference × 5
- dsl × 5
- formlet × 5
- java × 5
- list × 5
- metaprogramming × 5
- ml × 5
- released in Zafir.4.0.188.91-beta10 × 5
- sql × 5
- visualstudio × 5
- websharper.forms × 5
- zafir × 5
- 4.0.192.106 × 4
- 4.0.195.127 × 4
- 4.1.0.38 × 4
- 4.2.1.86 × 4
- 4.2.6.118 × 4
- css × 4
- example × 4
- extensions × 4
- fsi × 4
- fsx × 4
- html5 × 4
- jqueryui × 4
- lift × 4
- reflection × 4
- remote × 4
- rest × 4
- spa × 4
- teaching × 4
- template × 4
- websocket × 4
- wontfix × 4
- 4.0.196.147 × 3
- 4.1.0.34 × 3
- 4.1.6.207 × 3
- 4.2.1.223-beta × 3
- 4.2.11.258 × 3
- 4.2.4.114 × 3
- 4.2.4.247 × 3
- 4.2.5.115 × 3
- 4.2.6.253 × 3
- 4.2.9.256 × 3
- ajax × 3
- alt.net × 3
- aml × 3
- asp.net mvc × 3
- canvas × 3
- cloudsharper × 3
- compilation × 3
- database × 3
- erlang × 3
- events × 3
- extension × 3
- file upload × 3
- forums × 3
- inline × 3
- issue × 3
- kendo × 3
- macro × 3
- mono × 3
- msbuild × 3
- mvc × 3
- pattern × 3
- piglet × 3
- released in Zafir.4.0.187.90-beta10 × 3
- svg × 3
- type provider × 3
- view × 3
- 4.1.1.64 × 2
- 4.1.5.203 × 2
- 4.1.7.232 × 2
- 4.2.10.257 × 2
- 4.2.3.111 × 2
- 4.2.5.249 × 2
- android × 2
- asp.net × 2
- beginner × 2
- blog × 2
- chart × 2
- client × 2
- client server app × 2
- clojure × 2
- computation expressions × 2
- constructor × 2
- corporate × 2
- courses × 2
- cufp × 2
- d3 × 2
- debugging × 2
- direct × 2
- discriminated union × 2
- docs × 2
- elm × 2
- endpoint × 2
- endpoints × 2
- enterprise × 2
- entity framework × 2
- event × 2
- f# interactive × 2
- fable × 2
- flowlet × 2
- formdata × 2
- forms × 2
- fsc × 2
- google maps × 2
- hosting × 2
- http × 2
- https × 2
- iis 8.0 × 2
- install × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- jquery mobile × 2
- keynote × 2
- lens × 2
- lenses × 2
- linux × 2
- listmodel × 2
- mac × 2
- numeric × 2
- oauth × 2
- obfuscation × 2
- offline × 2
- oop × 2
- osx × 2
- packaging × 2
- pattern matching × 2
- performance × 2
- pipelines × 2
- q&a × 2
- quotation × 2
- reference × 2
- released in Zafir.4.0.185.88-beta10 × 2
- rx × 2
- script × 2
- security × 2
- self host × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- tutorials × 2
- type × 2
- url × 2
- var × 2
- websharper.charting × 2
- websharper4 × 2
- websockets × 2
- wig × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- 4.0.194.126 × 1
- 4.1.3.184 × 1
- 4.1.4.189 × 1
- 4.2.0.214-beta × 1
- 4.2.12.259 × 1
- 4.2.2.231-beta × 1
- 4.2.8.255 × 1
- Canvas Sample Example × 1
- DynamicStyle Animated Style × 1
- Fixed in 4.0.190.100-rc × 1
- Released in Zafir.UI.Next.4.0.169.79-beta10 × 1
- SvgDynamicAttribute × 1
- WebComponent × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- actor × 1
- addin × 1
- agents × 1
- aggregation × 1
- agile × 1
- alter session × 1
- animation × 1
- anonymous object × 1
- apache × 1
- api × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net core × 1
- asp.net integration × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- attributes × 1
- authorization × 1
- b-tree × 1
- back button × 1
- badimageformatexception × 1
- bash script × 1
- batching × 1
- binding-vars × 1
- bistro × 1
- body × 1
- bundle × 1
- camtasia studio × 1
- cas protocol × 1
- charts × 1
- clarity × 1
- class × 1
- cli × 1
- clipboard × 1
- clojurescript × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- color zones × 1
- combinator × 1
- combinators × 1
- compile × 1
- compile code on server × 1
- config × 1
- confirm × 1
- content × 1
- context × 1
- context.usersession × 1
- continuation-passing style × 1
- coords × 1
- cordova × 1
- cors × 1
- coursera × 1
- cross-domain × 1
- csla × 1
- current_schema × 1
- custom content × 1
- data × 1
- data grid × 1
- datetime × 1
- debug × 1
- declarative × 1
- delete × 1
- devexpress × 1
- dhtmlx × 1
- dictionary × 1
- directattribute × 1
- disqus × 1
- distance × 1
- do binding × 1
- doc elt ui.next upgrade × 1
- docker × 1
- dojo × 1
- dol × 1
- dom × 1
- domain × 1
- du × 1
- duf-101 × 1
- dynamic × 1
- eastern language × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- enums × 1
- error × 1
- etw × 1
- euclidean × 1
- eventhandlerlist × 1
- examples × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- failed to translate × 1
- fake × 1
- fantomas × 1
- fear × 1
- float × 1
- form × 1
- form-data × 1
- forum × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- fsunit × 1
- function × 1
- functional style × 1
- game × 1
- games × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- getting-started × 1
- google × 1
- google.maps × 1
- grid × 1
- group × 1
- guide × 1
- hash × 1
- headers × 1
- hello world example × 1
- heroku × 1
- highchart × 1
- history × 1
- how to × 1
- html-templating × 1
- http405 × 1
- httpcontext × 1
- hubfs × 1
- i18n × 1
- ie 8 × 1
- if-doc × 1
- iis × 1
- image × 1
- images × 1
- inheritance × 1
- initialize × 1
- input × 1
- install "visual studio" × 1
- installer × 1
- int64 × 1
- interfaces × 1
- internet explorer × 1
- interop × 1
- interpreter × 1
- io × 1
- iobservable × 1
- ios × 1
- iot × 1
- ipad × 1
- isomorphic × 1
- javascript optimization × 1
- javascript semanticui resources × 1
- jquery-plugin × 1
- jquery-ui × 1
- jquery-ui-datepicker × 1
- js × 1
- kendo datasource × 1
- kendochart × 1
- kendoui compiler × 1
- knockout × 1
- l10n × 1
- learning × 1
- library × 1
- libs × 1
- license × 1
- licensing × 1
- lineserieszonescfg × 1
- local setting × 1
- localization × 1
- logging × 1
- loop × 1
- macros × 1
- mailboxprocessor × 1
- mapping × 1
- maps × 1
- markerclusterer × 1
- markup × 1
- marshal × 1
- math × 1
- mathjax × 1
- message × 1
- message passing × 1
- message-passing × 1
- meta × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- mix × 1
- mobile installation × 1
- mod_mono × 1
- modal × 1
- module × 1
- mouseevent × 1
- mouseposition × 1
- multidimensional × 1
- multiline × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- native × 1
- nested × 1
- nested loops × 1
- node × 1
- nunit × 1
- object relation mapper × 1
- object-oriented × 1
- om × 1
- onboarding × 1
- onclick × 1
- optimization × 1
- option × 1
- orm × 1
- os x × 1
- output-path × 1
- override × 1
- paper × 1
- parameter × 1
- persistence × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- post × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- privacy × 1
- private × 1
- profile × 1
- programming × 1
- project × 1
- project euler × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- proxy × 1
- ptvs × 1
- public × 1
- pure f# × 1
- purescript × 1
- qna × 1
- quant × 1
- query sitelet × 1
- question × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- reactjs × 1
- real-time × 1
- ref × 1
- region × 1
- released in 4.0.190.100-rc × 1
- reporting × 1
- responsive design × 1
- rest api × 1
- rest sitelet × 1
- restful × 1
- round table × 1
- router × 1
- routing × 1
- rpc reverseproxy × 1
- runtime × 1
- sales × 1
- sample × 1
- sampleapp × 1
- scriptcs × 1
- scripting × 1
- search × 1
- self hosted × 1
- semanticui × 1
- sequence × 1
- serialisation × 1
- service × 1
- session-state × 1
- sharepoint × 1
- signals × 1
- sitelet website × 1
- sitelet.protect × 1
- sitlets × 1
- slickgrid × 1
- source code × 1
- sqlentityconnection × 1
- ssl × 1
- standards × 1
- static content × 1
- stickynotes × 1
- streamreader × 1
- stress × 1
- strong name × 1
- structures × 1
- submitbutton × 1
- subscribe × 1
- svg example html5 websharper.ui.next × 1
- sweetalert × 1
- system.datetime × 1
- system.reflection.targetinvocationexception × 1
- table storage × 1
- targets × 1
- tdd × 1
- templates ui.next × 1
- templating × 1
- text parsing × 1
- three.js × 1
- time travel × 1
- tls × 1
- tooltip × 1
- tracing × 1
- tsunamiide × 1
- turkish × 1
- twitter-bootstrap × 1
- type erasure × 1
- type inference × 1
- type providers × 1
- type-providers × 1
- typeprovider × 1
- ui next forms × 1
- ui-next × 1
- ui.next jqueryui × 1
- ui.next charting × 1
- ui.next formlets × 1
- ui.next forms × 1
- ui.next suave visualstudio × 1
- ui.next templating × 1
- unicode × 1
- unittest client × 1
- upload × 1
- usersession × 1
- validation × 1
- vb × 1
- vb.net × 1
- vector × 1
- view.map × 1
- visal studio × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio 2012 × 1
- visual studio shell × 1
- vs2017 compiler zafir × 1
- vsix × 1
- web api × 1
- web-scraping × 1
- webapi × 1
- webcomponents × 1
- webforms × 1
- webgl × 1
- webrtc × 1
- webshaper × 1
- websharper async × 1
- websharper codemirror × 1
- websharper f# google × 1
- websharper forms × 1
- websharper reactive × 1
- websharper rpc × 1
- websharper sitelets routing × 1
- websharper warp × 1
- websharper-interface-generator × 1
- websharper.chartsjs × 1
- websharper.com × 1
- websharper.exe × 1
- websharper.owin × 1
- websharper.ui.next × 1
- websharper.ui.next jquery × 1
- websockets iis × 1
- why-websharper × 1
- windows 7 × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- www.grabbitmedia.com × 1
- xamarin × 1
- xml × 1
- yeoman × 1
- yield × 1
- zafir beta × 1
- zafir websharper4 × 1
- zarovizsga × 1
![]() |
Copyright (c) 2011-2012 IntelliFactory. All rights reserved. Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy |
Built with WebSharper |
Hi
Is there a technical restriction that's preventing us from having immutable models of pure F# data structures without having Var<'T> everywhere? Elm seems beautiful and it looks very similar to F#.
Thanks, Kurren