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.

By on 3/11/2016 9:00 AM ()

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.

By on 3/13/2016 4:55 AM ()

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?

By on 3/13/2016 9:25 AM ()

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.

By on 3/17/2016 7:54 AM ()

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....

By on 4/23/2016 5:20 AM ()

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 Vars and Views 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 Vars 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.

By on 4/27/2016 3:53 AM ()

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.

By on 4/30/2016 1:25 AM ()

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.

By on 3/13/2016 1:51 AM ()

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 Vars 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.

By on 3/11/2016 3:02 AM ()

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!

By on 3/13/2016 1:49 AM ()

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.

By on 3/13/2016 3:35 AM ()
IntelliFactory Offices 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