Skip to content

🍦 VanFS: 1:1 bindings from F# to 🍦VanJS (an ultra-lightweight , zero-dependency , and unopinionated Reactive UI framework based on pure vanilla JavaScript and DOM without React/JSX) + WebComponents + micro FRP

License

Notifications You must be signed in to change notification settings

ken-okabe/vanfs

Repository files navigation

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
vanfs

🍦 VanFS

image

VanFS is a project template for 1:1 bindings from F# to VanJS (A tiny Reactive UI Framework without React/JSX) + WebComponents + micro FRP (Functional reactive programming)

What is VanJS?

image

https://github.com/vanjs-org/van

🍦 VanJS: World's smallest reactive UI framework. Incredibly Powerful, Insanely Small - Everyone can build a useful UI app in an hour.

https://vanjs.org

VanJS (abbreviated Vanilla JavaScript) is an ultra-lightweight , zero-dependency , and unopinionated Reactive UI framework based on pure vanilla JavaScript and DOM. Programming with VanJS feels like building React apps in a scripting language, without JSX. Check-out the Hello World code below:

VanJS code in JavaScript

import van from "vanjs-core"

const { a, p, div, li, ul } = van.tags
// Reusable components can be just pure vanilla JavaScript functions.
// Here we capitalize the first letter to follow React conventions.
const Hello =
    () =>
        div(
            p("👋Hello"),
            ul(
                li("🗺️World"),
                li(a({ href: "https://vanjs.org/" }, "🍦VanJS")),
            ),
        )

van.add(document.body, Hello())

Try on jsfiddle

VanFS is a F# project template for one-to-one direct bindings of VanJS

VanFS code in F#

module HelloApp
open Browser
open Browser.Types
open Fable.Core.JsInterop
open Van.Basic // import tags, add

let a: Tag = tags?a
let p: Tag = tags?p
let div: Tag = tags?div
let ul: Tag = tags?ul
let li: Tag = tags?li

let Hello =
    fun _ ->
        div [
            p ["👋Hello"]
            ul [
                li ["🗺️World"]
                li [a [{|href="https://vanjs.org/"|}; "🍦VanJS"]]
            ]
        ]

add [document.body; Hello()]
|> ignore

Demo

https://codepen.io/kentechgeek/pen/VwNOVOx

image

Components with parameters

VanJS components are just functions in JavaScript.

VanFS components are just functions in F#, and there are no strict rules like functional components in React.

However, if we aim for a consistent style for components with parameters that is similar to:

a [{|href="https://vanjs.org/"|}; "🍦VanJS"]

the following code should be more appropriate:

let Greeting: Tag =
    fun list ->
        let name: string = list[0]?name
        div [$"Hello {name}!"]

add [document.body; Greeting [{|name="Ken"|}]]
|> ignore

image

Here is the corresponding TSX code:

const Greeting: Component<{ name: string }> =
    ({ name }) => 
        <div>Hello {name}!</div>;

render(() => <Greeting name="Ken" />, document.body);

Why VanJS is based on Vanilla JavaScript

image

VanJS: About - the Story behind VanJS

But I think, in a nutshell, the best way to describe it is: VanJS is the scripting language for UI, just like bash is the scripting language for terminal.

Being the scripting language for UI , is the fundamental principle that guides the design of VanJS . It's based on JavaScript so that it can work in as many environments as possibles, not only for websites, but also for webviews which most major OSes support.

VanJS: About - How Did VanJS Get Its Name?

Under the hood, VanJS stays truthful to Vanilla JavaScript as close as possible, as there is no transpiling, virtual DOM or any hidden logic. VanJS code can be translated to Vanilla JavaScript code in a very straightforward way.

Why we should avoid using JavaScript

VanJS is a library based on Vanilla JavaScript for the well-established reasons.

image

However, to take full advantage of VanJS , we should consider using alternative languages instead of JavaScript , which are commonly referred to as AltJS .

One of the critical reasons is that JavaScript is not a type-safe language , which can lead to runtime errors and bugs.

The Effect of Programming Language On Software Quality

An Experiment About Static and Dynamic Type Systems Doubts About the Positive Impact of Static Type Systems on Development Time

Most notably, it does appear that strong typing is modestly better than weak typing, and among functional languages, static typing is also somewhat better than dynamic typing. We also find that functional languages are somewhat better than procedural languages. It is worth noting that these modest effects arising from language design are overwhelmingly dominated by the process factors such as project size, team size, and commit size. However, we hasten to caution the reader that even these modest effects might quite possibly be due to other, intangible process factors, e.g., the preference of certain personality types for functional, static and strongly typed languages.

In fact, in modern web development, JavaScript has increasingly become a compile target from other languages, such as TypeScript.

image

TypeScript -> JavaScript

VanJS can be regarded as a compile target from VanFS (AltJS)

VanFS (AltJS) -> VanJS

image

VanFS project includes some TypeScript code.


1. The internal mechanism of VanFS

https://github.com/ken-okabe/vanfs/blob/main/van-api/ts/basic.ts

TS code for the purpose of conversion using JS Proxy:

// unary function ([a,b,c,...]) in F#  
// -> n-ary function (a,b,c,...) in VanJS

This is under the van-api directory which is essential and we would not want to modify it to keep things working.


2. For styles and Web Components

Users must install any required CSS or Web Components .

VanJS does not provide the specific installation support beause it's just a VanillaJS.

On the other hand, VanFS clarifies the step-by-step process as below:

CSS

Everything we need to customize or import is located under web-imports directory.

image

Import and Register the web components

/web-imports/components.ts

import {
    provideFluentDesignSystem,
    fluentCard,
    fluentCheckbox
} from "@fluentui/web-components";

provideFluentDesignSystem()
    .register(
        fluentCard()
    );

provideFluentDesignSystem()
    .register(
        fluentCheckbox()
    );
/web-imports/css-urls.ts

export let cssURLs = [
 "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
];

Regardless, all the required code within the VanFS project is compiled into a single VanillaJS bundle using Fable and Vite.

image

See: 🚀 Getting Started

Why we should avoid using TypeScript and migrate to F#

image

F# gives you simplicity and succinctness like Python with correctness , robustness and performance beyond C# or Java.

Undoubtedly, TypeScript is the most commonly used AltJS. It is a superset of JavaScript that adds type safety and other features. So why not use TypeScript?

There are many reasons, but chief among them is developer productivity .

For instance, the below are the identical code written in TypeScript and F#.

let bindT = <A, B>
    (monadf: (a: A) => Timeline<B>) =>
    (timelineA: Timeline<A>): Timeline<B> => {
        let timelineB = monadf(timelineA.lastVal);
        let newFn = (a: A) => {
            nextT(monadf(a).lastVal)(timelineB);
            return undefined;
        };
        timelineA.lastFns = timelineA.lastFns.concat([newFn]);
        return timelineB;
    };

In TypeScript, compared with legacy JavaScript, an additional step is required to add type signatures to all variables, functions, and parameters. This is often overwhelming.

let bindT =
    fun monadf timelineA ->
        let timelineB = timelineA.lastVal |> monadf
        let newFn =
            fun a ->
                timelineB
                |> nextT (a |> monadf).lastVal
                |> ignore
        timelineA.lastFns <- timelineA.lastFns @ [ newFn ]
        timelineB

The F# code is much cleaner and more readable than TypeScript code.

In F#, we rarely need to add types manually thanks to its powerful type inference. This makes F# development feel similar to legacy JavaScript coding.

In reality, it is much more than that.

image

The powerful F# compiler automatically generates type annotations in VSCode editor, eliminating the need for manual typing that TypeScript demands.

While programmers may want to define fundamental object types that form the backbone of their code, in other places, if the F# compiler warns for a demand of manual type annotations, usually, something is wrong .

In F#, if the compiler cannot infer the type, it often suggests that there may be mathematical inconsistencies.

In TypeScript, if the compiler cannot infer the type, it often suggests limitations in its type inference capabilities. This makes it hard to determine the precise cause of the problem.

As a result, F# programmers are naturally led to write mathematically consistent and rigorous code; unfortunately, this benefit rarely happens in TypeScript.

F# as an AltJS: A Comparison with TypeScript

F# is generally recognized as running on the .NET Framework, but just as TypeScript is compiled to JavaScript, F# is also compiled to JavaScript.

  • TypeScript -> JavaScript

  • F# -> JavaScript

More precisely,

TypeScirpt
TypeScript Compiler running on Node.js (npx tsc)
JavaScript running in the browser

F#
Fable Compiler running on .NET (dotnet fable)
JavaScript running in the browser

Therefore, the backbone of VanFS is Fable.

image

Fable enables F# code to be compiled to JavaScript and run in the browser.

Why browser? Why VanJS?

There are a lot of Why s here!

I've created a separate article on this topic since it's part of the larger frontend app development landscape and deserves a focused discussion with my own opinions.

image

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
getting-started

🚀 Getting Started

Build a VanFS project template

Prerequisites

If you are new to F# and using VSCode, read F# Settings on VSCode.


A VanFS/Fable project is a hybrid of F#.NET project and npm project .

See Fable Setup Documentaion


Quick Start

git clone https://github.com/ken-okabe/vanfs
cd vanfs
dotnet restore  # .NET project setup
dotnet tool restore
npm i           #  npm project setup

image


or Create a project from scratch

image

mkdir my-project
cd my-project
dotnet new console -lang F#
dotnet new tool-manifest
dotnet tool install fable
dotnet add package Fable.Core
dotnet add package Fable.Browser.Dom
npm init -y
npm i -D vanjs-core
npm i -D vite
# Copy&Place `van-api` `web-imports` directory
# Copy&Place `index.html` `Program.fs` `vite.config.ts` file
# modify `my-project.fsproj`
#  <ItemGroup>
#    <Compile Include="van-api/fs/*.fs" />
#    <Compile Include="Program.fs" />
#  </ItemGroup>

/my-project.fsproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>my_project</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="van-api/fs/*.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Fable.Browser.Dom" Version="2.16.0" />
    <PackageReference Include="Fable.Core" Version="4.3.0" />
  </ItemGroup>

</Project>

Compile F# to JavaScript

dotnet fable watch

Live Preview with Vite

npx vite

image

image

Build with Vite

npx vite build

The production files will be under build directory.

image

CSS

Everything we need to customize or import is located under web-imports directory.

image

/web-imports/custom.css

body {
    font-family: sans-serif;
    padding: 1em;
    background-color: beige;
  }

Demo

https://codepen.io/kentechgeek/pen/zYXQyxz

image

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
web-components

🌐 Web Components

VanFS can leverage custom HTML tags provided by Web Components with design systems : Microsoft Fluent, Google Material Design, etc. .


image

image

https://github.com/microsoft/fluentui/

Install the Fluent UI Web Components with NPM or Alternatives

https://www.npmjs.com/package/@fluentui/web-components

npm install @fluentui/web-components

Import and Register the web components

Let's use Fruent Web Card and Checkbox.

https://learn.microsoft.com/en-us/fluent-ui/web-components/components/card?pivots=typescript

https://learn.microsoft.com/en-us/fluent-ui/web-components/components/checkbox?pivots=typescript

image

/web-imports/components.ts

import {
    provideFluentDesignSystem,
    fluentCard,
    fluentCheckbox
} from "@fluentui/web-components";

provideFluentDesignSystem()
    .register(
        fluentCard()
    );

provideFluentDesignSystem()
    .register(
        fluentCheckbox()
    );

Some Web Components use CSS

image

/web-imports/custom.css

body {
    font-family: sans-serif;
    padding: 1em;
    background-color: beige;
  }

.custom {
    --card-width: 200px;
    --card-height: 150px;
    padding: 22px;
  }

Use Web Components from Program.fs

Program.fs

module WebComponentsApp
open Browser
open Browser.Types
open Fable.Core.JsInterop
open Van.Basic // import tags, add

let br : Tag = tags?br

// Define the fluent-card and fluent-checkbox tags
let fluentCard: Tag = tags?``fluent-card``
let fluentCheckbox: Tag = tags?``fluent-checkbox``

let List =
    fun _ ->
        fluentCard [
            {|``class``="custom"|}
            // class is a reserved word in F#
            // so we use backticks to escape it
            fluentCheckbox ["Did you check this?"]
            br []
            fluentCheckbox [{|``checked``=true; disabled=true|}; "Is this disabled?"]
            br []
            fluentCheckbox [{|``checked``=true|}; "Checked by default?" ]
        ]

add [document.body; List()]
|> ignore

Clean the fable project and compile again

When major changes are made, cleaning the Fable project is sometimes necessary.

dotnet fable clean

dotnet fable watch

Live Preview with Vite

npx vite

image


image

https://material-web.dev/about/intro/

image

https://github.com/material-components/material-web

Install the Material Web Components with NPM or Alternatives

https://material-web.dev/about/quick-start/

https://www.npmjs.com/package/@material/web

npm install @material/web

Import the web components

Let's use Material web Text field.

https://material-web.dev/components/text-field/

image

https://material-web.dev/components/text-field/stories/

image

image

/web-imports/components.ts

import '@material/web/textfield/filled-text-field.js';
import '@material/web/button/text-button.js';
import '@material/web/button/outlined-button.js';

/web-imports/custom.css

.custom3 {
    --card-width: 460px;
    --card-height: 150px;
    padding: 20px;
  }

.row {
  align-items: flex-start;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.buttons {
  justify-content: flex-end;
  padding: 16px;
}

md-filled-text-field,
md-outlined-text-field {
  width: 200px;
}

Program.fs

module MaterialUI
open Browser
open Browser.Types
open Fable.Core.JsInterop
open Van.Basic // import tags, add

let div: Tag = tags?div
let form: Tag = tags?form

let fluentCard: Tag = tags?``fluent-card``
let mdFilledTextField: Tag = tags?``md-filled-text-field``
let mdTextButton: Tag = tags?``md-text-button``
let mdOutlinedButton: Tag = tags?``md-outlined-button``

let Form =
    fun _ ->
        fluentCard [
            {|``class``="custom3"|}
            form [
                div [
                    {|``class``="row"|}
                    mdFilledTextField [
                        {|
                        label="First name"
                        name="first-name"
                        required=""
                        autocomplete="given-name"
                        |}
                    ]
                    mdFilledTextField [
                        {|
                        label="Last name"
                        name="last-name"
                        required=""
                        autocomplete="family-name"
                        |}
                    ]
                ]
                div [
                    {|``class``="row buttons"|}
                    mdTextButton [
                        {|``type``= "reset"|}
                        "Reset"
                    ]
                    mdOutlinedButton [
                        {|``type``= "submit"|}
                        "Submit"
                    ]
                ]
            ]
        ]

add [document.body; Form()]
|> ignore

Build with Vite

npx vite build

Demo

https://codepen.io/kentechgeek/pen/KKYLwgN?editors=1111

image


Import the web components

Let's use Material web Icon Buttons.

https://material-web.dev/components/icon-button/

image

image

/web-imports/components.ts

import '@material/web/icon/icon.js';
import '@material/web/iconbutton/icon-button.js';

Material Web Components use Google Fonts/Icons

https://m3.material.io/styles/icons/overview

image

https://fonts.google.com/icons

image

Install Google Fonts/Icons

Obtain required CSS URLs from the Google Fonts page.

https://fonts.google.com/icons?selected=Material+Symbols+Outlined:star:FILL@0;wght@400;GRAD@0;opsz@24

image

Add the CSS URL to

image

/web-imports/css-urls.ts

export let cssURLs = [
 "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
];

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
frp

⚡️ Functional Reactive Programming (FRP)

(1:1 bindings for composing UIs) + micro FRP

VanFS is described as


1:1 bindings from F# to VanJS (A tiny Reactive UI Framework without React/JSX) + WebComponents + micro FRP

or

VanFS is a F# project template for one-to-one direct bindings of VanJS


1:1 bindings is absolutely true within the scope of the basic features for composing UIs, but not a case for its state management.

VanJS is a framework that embraces Reactive Programming

VanJS reactively binds its state objects to corresponding DOM elements. This means that when a state object updates, the corresponding DOM element automatically updates as well. This approach is a common feature among declarative UI libraries such as React, SolidJS, etc.

image

VanJS state API

In order to utilize the state management, VanJS provides two APIs: van.state and van.derive.

image

image

image

VanFS is a framework that embraces Functional Reactive Programming (FRP)

VanJS is Reactive.

VanFS is Functional Reactive.

What is Functional Programming?

Given the critical significance of Functional Programming in modern software development, I have dedicated a separate article to exploring its key concepts and benefits.

VanFS provides binary operations to utilize the state management

In Functional Programming, everything is an expression or operation (💡 What is Functional Programming?). Accordingly, VanFS provides binary operations for the reactive state management .

$$ TimelineA ~ ~ * ~ ~ Function \quad \rightarrow \quad TimelineB $$

$$ TimelineB \quad = \quad TimelineA ~ ~ * ~ ~ Function $$

This binary operation corresponds to an operation in spreadsheet apps.

image

image

  • $TimelineA \quad = \quad$ A1

  • $TimelineB \quad = \quad$ B1

  • $Function \quad = \quad$ fx

This is the identical structure of:

$$ ListA ~ ~ * ~ ~ Function \quad \rightarrow \quad ListB $$

$$ ListB \quad = \quad ListA ~ ~ * ~ ~ Function $$

image

image

So, this is FRP.

Functional Reactive Programming (FRP) is a programming paradigm that uses mathematical expressions, specifically binary operations , as a means of implementing Reactive Programming .

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
timeline

⏱️ Timeline

Timeline is a fundamentally standalone FRP library, with no dependencies on VanJS or F# asynchronous features. The codebase is a compact pure function implementation of approximately 30-40 lines of code.

image

TimelineElement is a modified version of Timeline, which wraps the State object of VanJS and serves as an additional module for utilizing the state management of VanFS.

🔍 Type

Timeline<'a>

record Timeline<'a>
  val mutable lastVal: 'a
  val el: StateElement<'a>
Field Description Van.state
lastVal Last value of the Timeline State.val
el Reactive DOM element of the Timeline State

🔍 Functions


① Function to initialize Timeline<'a>

Timeline

'a -> Timeline<'a>

VanJS

let counter = van.state(0);

console.log(counter.val);
// 0

VanFS

let counter = Timeline 0

console.log counter.lastVal
// 0

Consider the Timeline as a specific container for a value, similar to a Cell in spreadsheet apps.

image


② Functions for the binary operations

$$ TimelineA ~ ~ * ~ ~ Function \quad \rightarrow \quad TimelineB $$

mapT

('a -> 'b) -> (Timeline<'a> -> Timeline<'b>)

bindT

('a -> Timeline<'b>) -> (Timeline<'a> -> Timeline<'b>)

VanFS

When the binary operator: $*$ is mapT,

$$ TimelineB \quad = \quad TimelineA \quad \triangleright mapT \quad double $$

let double = fun a -> a * 2

let timelineA = Timeline 1

let timelineB =
    timelineA |> mapT double

console.log timelineB.lastVal
// 2

This code for the binary operation simply corresponds to the basic usage of spreadsheet apps

image

This is the identical structure of:

$$ ListB \quad = \quad ListA \quad \triangleright List.map \quad double $$

let double = a => a * 2;

let arrayA = [1];

let arrayB =
    arrayA.map(double);

console.log(arrayB);
// [2]

let double =
    fun a -> a * 2

let listA = [1]

let listB =
    listA |> List.map double

console.log listB
// [2]

We could recognize the array [2] is identical to the Cell and Value 2 of a spreadsheet; however, the spreadsheet and Timeline maintain a double relationship as values change over the timeline .


③ Function to update Timeline<'a>

$$TimelineA \quad \triangleright nextT \quad newValue \quad \rightarrow \quad TimelineA'$$

nextT

'a -> Timeline<'a> -> Timeline<'a>

$$ TimelineA' \quad = \quad TimelineA \quad \triangleright nextT \quad newValue $$

let timelineA' =
    timelineA |> nextT 3

or, in most cases, we don’t need another timelineA' and want to discard it, so simply ignore the returned value.

let timelineA = Timeline 1

timelineA
|> nextT 3
|> ignore

console.log timelineA.lastVal
// 3

image


①②③ action of Timeline<'a>

The update to timelineA will trigger a reactive update of timelineB according to the rule defined by the binary operation.

image

let double = fun a -> a * 2

// ① initialize timelineA
let timelineA = Timeline 1

// confirm the lastVal of timelineA
console.log timelineA.lastVal
// 1

// ② the binary operation
let timelineB =
    timelineA |> mapT double

// confirm the lastVal of timelineB
console.log timelineB.lastVal
// 2

//=====================================
// ③ update the lastVal of timelineA
timelineA
|> nextT 3
|> ignore
// update to timelineA will trigger
//   a reactive update of timelineB

// confirm the lastVal of timelineA & timelineB
console.log timelineA.lastVal
// 3
console.log timelineB.lastVal
// 6

Counter app

VanJS

import van from "vanjs-core"

const { button, div, h2 } = van.tags

const Counter =
    () => {
        const counter = van.state(0)

        van.derive(() =>
            console.log(`Counter: ${counter.val}`))

        return div(
            h2("❤️ ", counter),
            button(
                {
                    onclick: () => ++counter.val
                },
                "👍"
            ),
            button(
                {
                    onclick: () => --counter.val
                },
                "👎"
            ),
        )
    }

van.add(document.body, Counter())

VanFS

Program.fs

module CounterApp

open Browser
open Browser.Types
open Fable.Core.JsInterop

open Van.Basic // import tags, add
open Van.TimelineElement // import Timeline

let div: Tag = tags?div
let h2: Tag = tags?h2
let icon: Tag = tags?``md-icon``
let iconButton: Tag = tags?``md-icon-button``

let Counter =
    fun _ ->
        let counter = Timeline 0 // ① initialize an Timeline

        counter // ② the binary operation of the Timeline
        |> mapT (fun value ->
                     console.log $"Counter: {value}")
        |> ignore
        // ignore the return value of `console.log`

        div [
            h2 ["❤️ "; counter.el] // 👈 `counter.el`
            iconButton [           // for Reactive DOM element
                {|onclick = fun _ ->
                                counter // ③ update the Timeline
                                |> nextT (counter.lastVal + 1)
                |}
                icon ["thumb_up"]
            ]
            iconButton [
                {|onclick = fun _ ->
                                counter // ③ update the Timeline
                                |> nextT (counter.lastVal - 1)
                |}
                icon ["thumb_down"]
            ]
        ]

add [document.body; Counter()]
|> ignore

Demo

https://codepen.io/kentechgeek/pen/gOyqNqb?editors=1111

image

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
nullable

⏱️ Nullable Types

What is Null?

Given the critical significance of Null in modern software development, I have dedicated a separate article to exploring its key concepts and benefits.

Nullable types in F#

Nullable value types in F#

A nullable value type Nullable<'T> represents any struct type that could also be null. This is helpful when interacting with libraries and components that may choose to represent these kinds of types, like integers, with a null value for efficiency reasons. The underlying type that backs this construct is System.Nullable.

can only represents struct type, which limitation is problematic.

Using Nullable Reference Types in F#

F#: How do I convert Option<'a> to Nullable, also when 'a can be System.String?

It would be nice if we could write any Nullable types including reference types in F#.

F# RFC FS-1060 - Nullable Reference Types

image

Currently, this syntax will generate an error.

Therefore, here's the alternative implementation:

https://github.com/ken-okabe/vanfs/blob/main/timeline/x-nullable.fs

NullableT

type NullableT<'a> =
    | Null
    | T of 'a
    member this.Value
            = match this with
            | Null -> failwith "Value is null"
            | T a -> a

let NullableT a =
    match box a with
    | :? NullableT<'a> as nullable -> nullable
    | _ -> T a

let nullable1 =
    Null

let nullable2 =
    NullableT "hello"

log nullable1
// Null
log nullable2
// T hello
log nullable2.Value
// hello

This specification resembles F#'s native nullable value types, but unlike it, NullableT can also represent any reference types.


F# Nullness support may come soon!

a preview of the work being done on the F# compiler to support .NET's nullness capabilities.

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
timeline-nullable

⏱️ Timeline Nullable

By utilizing the Nullable type , we can provide new operators that pair with Timeline .

Initializing a Timeline with Null value, the provided function remains unexecuted and waits in a pending state. Once the Timeline value is updated to a non Null value by a valid event, the function is then triggered and executed.

🔍 Functions


① Function to initialize Timeline<NullableT<'a>>

Timeline

NullableT<'a> -> Timeline<NullableT<'a>>

let timelineNullable = Timeline Null

log timelineNullable.lastVal // use `log` of Timeline
// Null

Consider this Timeline as an empty Cell in spreadsheet apps.

image

🔍 Type

Timeline type and the function:

① Function to initialize Timeline<'a>

① Function to initialize Timeline<NullableT<'a>>

is the same entity .

Consider Timeline can accept any generic types of 'a including NullableT<'a>.

On the other hand, in the case of Timeline<NullableT<'a>> where the parameter value is a nullable type, if we need the Timeline behavior to ignore the provided function and simply pass through the Null value when the parameter is Null , we can use specific operators as shown below.

② Functions for the binary operations

$$ TimelineA ~ ~ * ~ ~ Function \quad \rightarrow \quad TimelineB $$

mapTN

(NullableT<'a> -> NullableT<'b>) -> (Timeline<NullableT<'a>> -> Timeline<NullableT<'b>>)

bindTN

(NullableT<'a> ->  Timeline<NullableT<'b>>) -> (Timeline<NullableT<'a>> -> Timeline<NullableT<'b>>)

When the binary operator: $*$ is mapT,

$$ TimelineB \quad = \quad TimelineA \quad \triangleright mapTN \quad double $$

let double =
    fun a -> NullableT (a * 2)

// ① initialize timelineA
let timelineA = Timeline Null

log timelineA.lastVal // use `log` of Timeline
// Null

// ② the binary operation
let timelineB =
    timelineA |> mapTN double
// currently, `timelineA = Timeline Null`
// so, `double` function is ignored
// and `timelineB` value becomes `Null`
log timelineB.lastVal // use `log` of Timeline
// Null

This code for the binary operation simply corresponds to the basic usage of spreadsheet apps.

image

The operator behaves similarly to JavaScript's Promise or its syntactic sugar, async-await.

③ Function to update Timeline<NullableT<'a>>

$$TimelineA \quad \triangleright nextTN \quad newNullableValue \quad \rightarrow \quad TimelineA'$$

nextTN

NullableT<'a> -> Timeline<NullableT<'a>> -> Timeline<NullableT<'a>>

$$ TimelineA' \quad = \quad TimelineA \quad \triangleright nextTN \quad newNullableValue $$

image

image

let timelineA' =
    timelineA |> nextTN (NullableT 3)

or, in most cases, we don’t need another timelineA' and want to discard it, so simply ignore the returned value.

①②③ action of Timeline<'a>

image

The update to timelineA will trigger a reactive update of timelineB according to the rule defined by the binary operation.

let double =
    fun a -> NullableT (a * 2)

// ① initialize timelineA
let timelineA = Timeline Null

log timelineA.lastVal // use `log` of Timeline
// Null

// ② the binary operation
let timelineB =
    timelineA |> mapTN double
// currently, `timelineA = Timeline Null`
// so, `double` function is ignored
// and `timelineB` value becomes `Null`
log timelineB.lastVal // use `log` of Timeline
// Null

// ③ update the lastVal of timelineA
timelineA
|> nextTN (NullableT 3)
|> ignore

log timelineA.lastVal // use `log` of Timeline
// T 3

// Now, `timelineA` value is updated to non `Null` value
// Accordingly, `timelineB` reactively becomes `double` of it
log timelineB.lastVal
// T 6

Program.fs

module Number

open Browser
open Browser.Types
open Fable.Core.JsInterop

open Van.Basic // import tags, add
open Van.TimelineElement // import Timeline
open Van.TimelineElementNullable // import Timelinetc.
open Van.Nullable // import NullableT
open Van.TimelineElementTask

let h4: Tag = tags?h4
let fluentCard: Tag = tags?``fluent-card``
let fluentTextField: Tag = tags?``fluent-text-field``

let Number =
    fun _ ->
        let number = Timeline Null

        let numberX2 =
            number
            |> mapTN (fun n -> NullableT(n * 2)) //System.Nullable

        fluentCard [
            {|``class``="custom1"|}

            h4 [ "Number" ]
            fluentTextField [
                {|
                appearance="outline"
                required=true
                ``type``="number"
                placeholder="1"
                oninput=
                    fun e ->
                        let value =
                            if e?target?value=""
                            then Null
                            else NullableT e?target?value

                        if value=Null // clear the output textField
                        then numberX2
                             |> nextTN Null
                             |> ignore
                             document.getElementById("output-field")?value
                                <- "Null" // clear the output textField
                        else ()

                        number
                        |> nextTN value
                        |> ignore
                |}
            ]

            h4 [ "Number × 2" ]
            fluentTextField [
                {|
                appearance="outline"
                readonly=true
                value=numberX2.el
                id="output-field"
                |}
            ]

        ]

add [document.body; Number()]
|> ignore

Demo

https://codepen.io/kentechgeek/pen/wvZNVzJ?editors=1111

image

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
timeline-task

⏱️ Timeline Task

While Timeline Nullable operators offer a basic principle similar to JavaScript's Promise, they are not capable of managing Task chaining, such as Promie.then.

Based on Timeline Nullable , we can obtain Timeline Task which is capable of Task chaining.

Task

Timeline<NullableT<'a>> -> 'b -> unit

 let task =
    fun timelineResult previousResult ->
        log "-----------task1 started..."
        log previousResult
        // delay-------------------------------
        let f = fun _ ->
            log ".......task1 done"
            timelineResult
            |> nextTN (NullableT 1)
            |> ignore
        setTimeout f 2000

taskT

Task<'a, NullableT<'a0>> -> Timeline<NullableT<'a>> -> Timeline<NullableT<'a>>

let timelineStarter =
    Timeline (NullableT 0)
    // tasks start immediately

timelineStarter
|> taskT task1
|> taskT task2
|> taskT task3
|> ignore

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
timeline-task-concat

⏱️ Timeline Task Concat

taskConcat or (+>)

(Task -> Task) -> Task

let task12 =
    task1 +> task2

let task123 =
    task1 +> task2 +> task3

let task1234 =
    task123 +> task4

Program.fs

module Tasks

open Browser
open Browser.Types
open Fable.Core.JsInterop

open Van.Basic // import tags, add
open Van.TimelineElement // import Timeline
open Van.TimelineElementNullable // import Timelinetc.
open Van.Nullable // import NullableT
open Van.TimelineElementTask
open Van.TimelineElementTaskConcat

open System.Timers
let setTimeout f delay =
    let timer = new Timer(float delay)
    timer.AutoReset <- false
    timer.Elapsed.Add(fun _ -> f())
    timer.Start()
let br: Tag = tags?``br``
let fluentCard: Tag = tags?``fluent-card``
let linerProgress: Tag = tags?``md-linear-progress``

let Tasks =
    fun _ ->
        let progressInit = false
        let progressStart = true
        let progressDone = false
        let percentInit = 0.0
        let percentStart = 0.0
        let percentDone = 1.0

        let timelineProgress1 = Timeline progressInit
        let timelineProgress2 = Timeline progressInit
        let timelineProgress3 = Timeline progressInit
        let timelinePercent1 = Timeline percentInit
        let timelinePercent2 = Timeline percentInit
        let timelinePercent3 = Timeline percentInit

        let taskStart =
            fun timelineProgress timelinePercent ->
                timelineProgress
                |> nextT progressStart
                |> ignore
                timelinePercent
                |> nextT percentStart
                |> ignore

        let taskDone =
            fun timelineProgress timelinePercent timelineResult->
                timelineProgress
                |> nextT progressDone
                |> ignore
                timelinePercent
                |> nextT percentDone
                |> ignore
                timelineResult
                |> nextTN (NullableT 999)
                |> ignore

        let task1 =
            fun timelineResult previousResult ->
                log "-----------task1 started..."
                taskStart timelineProgress1 timelinePercent1
                // delay-------------------------------
                let f = fun _ ->
                    log "...task1 done"
                    taskDone timelineProgress1 timelinePercent1 timelineResult
                setTimeout f 2500

        let task2 =
            fun timelineResult previousResult ->
                log "-----------task2 started..."
                taskStart timelineProgress2 timelinePercent2
                // delay-------------------------------
                let f = fun _ ->
                    log "...task2 done"
                    taskDone timelineProgress2 timelinePercent2 timelineResult
                setTimeout f 2500

        let task3 =
            fun timelineResult previousResult ->
                log "-----------task3 started..."
                taskStart timelineProgress3 timelinePercent3
                // delay-------------------------------
                let f = fun _ ->
                    log "...task3 done"
                    taskDone timelineProgress3 timelinePercent3 timelineResult
                setTimeout f 2500

        let timelineStarter = Timeline Null //tasks disabled initially

        let task123 =
            task1 +>
            task2 +>
            task3

        timelineStarter
        |> taskT task123
        |> ignore

(* task123 can be written as below

timelineStarter
|> taskT task1
|> taskT task2
|> taskT task3
|> ignore

*)
        let start =
            fun _ -> // timeline will start
                timelineStarter
                |> nextTN (NullableT 0)
                |> ignore

        setTimeout start 2000

        fluentCard [
            {|``class``="custom2"|}
            br []
            linerProgress [
                {|indeterminate=timelineProgress1.el
                  value=timelinePercent1.el|}
            ]
            br []
            linerProgress [
                {|indeterminate=timelineProgress2.el
                  value=timelinePercent2.el|}
            ]
            br []
            linerProgress [
                {|indeterminate=timelineProgress3.el
                  value=timelinePercent3.el|}
            ]
        ]

add [document.body; Tasks()]
|> ignore

Demo

https://codepen.io/kentechgeek/pen/jORdjvy?editors=1111

image


It's also possible to write a Console app without browserUIs.

Program.fs

module Tasks

open Van.TimelineElement // import Timeline
open Van.TimelineElementNullable // import Timelinetc.
open Van.Nullable // import NullableT
open Van.TimelineElementTask
open Van.TimelineElementTaskConcat

open System.Timers
let setTimeout f delay =
    let timer = new Timer(float delay)
    timer.AutoReset <- false
    timer.Elapsed.Add(fun _ -> f())
    timer.Start()

let nonNull = NullableT true // some non-null value

let task1 =
    fun timelineResult previousResult ->
        log "-----------task1 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task1 done"
            timelineResult
            |> nextTN nonNull
            |> ignore
        setTimeout f 2500

let task2 =
    fun timelineResult previousResult ->
        log "-----------task2 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task2 done"
            timelineResult
            |> nextTN nonNull
            |> ignore
        setTimeout f 1000

let task3 =
    fun timelineResult previousResult ->
        log "-----------task3 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task3 done"
            timelineResult
            |> nextTN nonNull
            |> ignore
        setTimeout f 3000

let timelineStarter = Timeline Null //tasks disabled initially

let task123 =
    task1 +>
    task2 +>
    task3

timelineStarter
|> taskT task123
|> ignore

(* task123 can be written as below

timelineStarter
|> taskT task1
|> taskT task2
|> taskT task3
|> ignore

*)

let start =
    fun _ -> // timeline will start
        timelineStarter
        |> nextTN nonNull
        |> ignore

setTimeout start 2000

Demo

https://codepen.io/kentechgeek/pen/BaEeyvL?editors=1111

image

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
timeline-task-or

⏱️ Timeline Task Or

taskOr or (+|)

(Task -> Task) -> Task

let task12 =
    task1 +| task2

let task123 =
    task1 +| task2 +| task3

let task1234 =
    task123 +| task4

Program.fs

module TaskOr

open Van.TimelineElement // import Timeline
open Van.TimelineElementNullable // import Timelinetc.
open Van.Nullable // import NullableT
open Van.TimelineElementTask
open Van.TimelineElementTaskOr

open System.Timers
let setTimeout f delay =
    let timer = new Timer(float delay)
    timer.AutoReset <- false
    timer.Elapsed.Add(fun _ -> f())
    timer.Start()

let nonNull = NullableT true

let task1 =
    fun timelineResult previousResult ->
        log "-----------task1 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task1 done"
            timelineResult
            |> nextTN (NullableT "task1")
            |> ignore
        setTimeout f 2500

let task2 =
    fun timelineResult previousResult ->
        log "-----------task2 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task2 done"
            timelineResult
            |> nextTN (NullableT "task2")
            |> ignore
        setTimeout f 1000

let task3 =
    fun timelineResult previousResult ->
        log "-----------task3 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task3 done"
            timelineResult
            |> nextTN (NullableT "task3")
            |> ignore
        setTimeout f 3000

let timelineStarter = Timeline Null //tasks disabled initially

let task123 =
    task1 +| task2 +| task3

let taskOutput =
    fun timelineResult (previousResult: NullableT<string>)
        ->  log ("The fastest result from: "
                + previousResult.Value)

timelineStarter
|> taskT task123 // Run all tasks then return the fastest result 
|> taskT taskOutput  // log the fastest result 
|> ignore

let start =
    fun _ -> // timeline will start
        timelineStarter
        |> nextTN nonNull
        |> ignore

setTimeout start 2000

Demo

https://codepen.io/kentechgeek/pen/zYXQGwQ?editors=1111

image

Contents
🍦 VanFS
  📱 Versatility of Web Technology
    for Cross-Platform App Development
🚀 Getting Started
🌐 Web Components
⚡️ Functional Reactive Programming (FRP)
  💡 What is Functional Programming?
  💡 How does Functional Programming Code Drive?
⏱️ Timeline
⏱️ Nullable Types
  💡 What is Null, Nullable and Option Types?
⏱️ Timeline Nullable
⏱️ Timeline Task
⏱️ Timeline Task Concat
⏱️ Timeline Task Or
⏱️ Timeline Task And
💬 Discussions
timeline-task-and

⏱️ Timeline Task And

taskAnd or (+&)

(Task -> Task) -> Task

let task12 =
    task1 +& task2

let task123 =
    task1 +& task2 +& task3

let task1234 =
    task123 +& task4

Program.fs

module TaskAnd

open Van.TimelineElement // import Timeline
open Van.TimelineElementNullable // import Timelinetc.
open Van.Nullable // import NullableT
open Van.TimelineElementTask
open Van.TimelineElementTaskAnd

open System.Timers
let setTimeout f delay =
    let timer = new Timer(float delay)
    timer.AutoReset <- false
    timer.Elapsed.Add(fun _ -> f())
    timer.Start()

let nonNull = NullableT true

let task1 =
    fun timelineResult previousResult ->
        log "-----------task1 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task1 done"
            timelineResult
            |> nextTN (NullableT "task1")
            |> ignore
        setTimeout f 2500

let task2 =
    fun timelineResult previousResult ->
        log "-----------task2 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task2 done"
            timelineResult
            |> nextTN (NullableT "task2")
            |> ignore
        setTimeout f 1000

let task3 =
    fun timelineResult previousResult ->
        log "-----------task3 started..."
        // delay-------------------------------
        let f = fun _ ->
            log "...task3 done"
            timelineResult
            |> nextTN (NullableT "task3")
            |> ignore
        setTimeout f 3000

let timelineStarter = Timeline Null //tasks disabled initially

let task123 =
    task1 +& task2 +& task3

let taskOutput =
    fun timelineResult (previousResult: NullableT<ListResult<'a>>)
        ->  log previousResult.Value.results

timelineStarter
|> taskT task123 // Run all tasks then return the list of results 
|> taskT taskOutput  // log the list of results 
|> ignore

let start =
    fun _ -> // timeline will start
        timelineStarter
        |> nextTN nonNull
        |> ignore

setTimeout start 2000

Demo

https://codepen.io/kentechgeek/pen/poBmJZq?editors=1111

image

About

🍦 VanFS: 1:1 bindings from F# to 🍦VanJS (an ultra-lightweight , zero-dependency , and unopinionated Reactive UI framework based on pure vanilla JavaScript and DOM without React/JSX) + WebComponents + micro FRP

Topics

Resources

License

Stars

Watchers

Forks