DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Universal Implementation of BFS, DFS, Dijkstra, and A-Star Algorithms
  • Queue in Data Structures and Algorithms
  • Writing an Interpreter: Implementation
  • Effective Java Collection Framework: Best Practices and Tips

Trending

  • The Convergence of Testing and Observability
  • A Guide to Data-Driven Design and Architecture
  • Microservices With Apache Camel and Quarkus (Part 5)
  • Generative AI: A New Tool in the Developer Toolbox

Singleton Implementation in F#

Continuing the Singleton journey, Ted Neward elaborates on implementing one in F#.

Ted Neward user avatar by
Ted Neward
·
Apr. 04, 16 · Tutorial
Like (4)
Save
Tweet
Share
5.16K Views

Join the DZone community and get the full member experience.

Join For Free

A Singleton implementation in F#.

Implementation: F

F#, being a functional/object hybrid language, makes it easy to do the traditional Singleton without much more work to do beyond what we see in languages like C++/Java/C#:

type Product private () =
  let mutable state = 0

  static let instance = Product()

  static member Instance = instance

  member this.DoSomething() = 
    state <- state + 1
    printfn "Doing something for the %i time" state
    ()

Product.Instance.DoSomething()
Product.Instance.DoSomething()
Product.Instance.DoSomething()

F# defines a "primary constructor" using the same top line of the type declaration as is occupied by the name, so in order to support the "singleton-ness" of this type, we use the "private" modifier to indicate that nobody else is allowed to construct an instance of Product. Since this is a Singleton, any instance state can be initialized inside the type (rather than through its constructor), particularly since the body of the type is "executed" as part of initializing the primary constructor.

Note that "Instance" is declared as a "member", which effectively obscures whether this is a property or a field or a method. This is a deliberate choice on the part of the language and essentially obviates the whole "field/method/property" discussion of the classic Singleton.

Initialization Note

We choose to use local let bindings for the instance values simply because it is much simpler to use in F# than to pass these through the private primary constructor.

Module Implementation

F# brings forward (from OCaml) the concept of strong modules (a module construct that is recognized and enforced by the language and runtime, instead of just convention), and modules support all forms of value bindings, meaning both fields and functions at the top level of the module. This then makes it relatively easy to write a Singleton as a module instead of as a class and is perhaps the preferred approach for more complex/larger Singletons. (For example, the module will almost certainly be the better approach for a Singleton that sits in front of a full Facade, for example.)

For example, consider:

module Singleton =

  type Product internal () =
    let mutable state = 0

    member this.DoSomething() =
      state <- state + 1
      printfn "Doing something for the %i time" state

  let Instance = Product()

In order to enforce the concept of Singleton-ness more carefully, we should probably mark the Product type itself as internal and implement a publicly-declared Product interface, so as to allow the maximum flexibility in this approach. This would make it much easier to later replace the Singleton with a variety of different possible options, including multiple instances and/or derived instances, without the client’s awareness.

Lazy Initialization

Like other languages that run on top of the CLR, F# will initialize types when the assembly is loaded into the process space and referenced for the first time; this means that the initialization of Product will occur at (roughly) the same time Instance is first used in most cases. Should some form of lazier initialization be required, the Instance member needs to be fleshed out into a method to do the does-the-instance-exist check, create a new instance, and hand it back. It’s not clear what benefit would be derived from doing so, however, in the simple case.

All-static Implementation

Of course, like most languages that support static methods, F# could simply be rewritten to use all static fields and methods, rather than hand back the singleton object instance upon which we invoke instance member operations.

Scopes

It is important to note that due to the implementation of the CLR, using statics (as above) will not actually be scoped to the entire CLR/process, but only to the AppDomain that loaded the class. This is discussed in more detail in a variety of different places, including books and papers that I have written, as well as numerous other sources. (However, with the fading favor of the AppDomain, this may be a moot point in a future version of the CLR.)

Concurrency

Being a language that runs on the CLR, F# has no stronger concurrency guarantees than C#, though it certainly prefers to take a different approach to the design of objects than C# has traditionally embraced (F# prefers to build immutable objects over mutable ones). However, given that the Singleton often holds state and that state tends to be immutable (after all, a constant global is often a compile-time constant and, therefore, no reason to use Singleton at all), it is generally safe to assume that any Singleton in F# will need to have some kind of concurrency control around it just as any C# or any other CLR-based language would.

Thread-local Scopes

As it turns out, a related concurrency example of Singletons is also an example of "scoping" Singletons differently than traditionally assumed. .NET 4.0 makes available a ThreadLocal class that will implicitly store the value, but store separate values per Thread. The docs demonstrate some C#-based usage:

using System;
using System.Threading;
using System.Threading.Tasks;

class ThreadLocalDemo
{
  // Demonstrates:
  //      ThreadLocal(T) constructor
  //      ThreadLocal(T).Value
  //      One usage of ThreadLocal(T)
  static void Main()
  {
      // Thread-Local variable that yields a name for a thread
      ThreadLocal<string> ThreadName = new ThreadLocal<string>(() =>
      {
          return "Thread" + Thread.CurrentThread.ManagedThreadId;
      });

      // Action that prints out ThreadName for the current thread
      Action action = () =>
      {
          // If ThreadName.IsValueCreated is true, it means that we are not the
          // first action to run on this thread.
          bool repeat = ThreadName.IsValueCreated;

          Console.WriteLine("ThreadName = {0} {1}", ThreadName.Value, repeat ? "(repeat)" : "");
      };

      // Launch eight of them.  On 4 cores or less, you should see some repeat ThreadNames
      Parallel.Invoke(action, action, action, action, action, action, action, action);

      // Dispose when you are done
      ThreadName.Dispose();
  }
}

Each time action is invoked, the "thread-specific Singleton" thread name will be returned.

This then makes it much easier for a Singleton implementation to manage a single instance per thread, rather than one instance across all threads.

Serialization

Note that the CLR is a platform which supports an opt-in automatic object serialization mechanism, and as such, if the Singleton is marked [Serializable], developers must take additional care to ensure that the Singleton, when deserialized, still remains the only instance. (Joshua Bloch talks about this in Effective Java; I’ve seen no .NET-specific book that discusses this issue in any degree of depth or detail, unfortunately.) However, it’s fair to ask why a Singleton might need to be serialized in the first place; if it is still determined that the Singleton must be serialized, then the follow-up question must be, “What happens when Singleton A is deserialized into a CLR where Singleton B already exists?” This becomes an important question around any state held in the two Singletons (which by itself, being a contradiction in terms, should be a red flag to the entire conversation), and whether that state should be merged, replaced, or cast aside. Danger Will Robinson, danger.

Implementation

Published at DZone with permission of Ted Neward, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Universal Implementation of BFS, DFS, Dijkstra, and A-Star Algorithms
  • Queue in Data Structures and Algorithms
  • Writing an Interpreter: Implementation
  • Effective Java Collection Framework: Best Practices and Tips

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: