ClojureDart

repository·main·Indexed 23 days ago

https://github.com/tensegritics/clojuredart

A toolchain for developing Flutter applications using the Clojure programming language. It enables the use of Clojure's functional paradigms within the Flutter/Dart ecosystem, providing a CLI for project initialization, hot reloading, and AOT compilation. The library includes the `cljd.flutter` module with the `f/widget` macro for declarative UI construction, `.child-threading` for widget nesting, and directives like `:watch`, `:managed`, and `:bind` for state and lifecycle management.

Tokens
15.2K
Snippets
59
Records
104
Agent score
82%

What's inside ClojureDart

  1. Overview of ClojureDart sample patterns

    main

    The samples in this repository demonstrate various ClojureDart idioms and Flutter widget patterns. Key patterns include:

    • State Management: Using :state for managing widget state.
    • Context & Inheritance: Using :inherit for Widget.of(context) or Theme.of(context), and :context for accessing BuildContext.
    • Resource Lifecycle: Using :with for initializing and disposing of resources.
    • Controllers: Using :controller for managing widget controllers (e.g., in TextField).
    • Key Management: Using :key for widget keys.
    • Binding: Using :bind for closures.
    • Extensions: Using reify :extends for anonymous class extensions.
    • Data Access: Using :get for accessing methods like ScaffoldMessenger.of(context).
  2. How CLJD munges Clojure names for Dart

    main

    CLJD converts Clojure symbols into valid Dart identifiers. Dart identifiers are restricted to [a-zA-Z0-9_$] and cannot start with a number or an underscore (which would make them private to the library).

    Munging Strategy:

    • Alphanumeric characters [a-zA-Z0-9] are left untouched.
    • The hyphen - is replaced by _. If the hyphen is the first character, it is replaced by $_.
    • All other characters (including $ and _) are escaped using specific escape sequences.

    Escape Sequences: All escape sequences start with $ and end with _.

    • Common punctuation: Escaped as $ALLCAPS_ (e.g., $COLON_).
    • Reserved words: Escaped as $themselves_ (e.g., $Function_).
    • Other characters: Escaped as $uXXXX_ where XXXX is 1 to 4 uppercase hexadecimal digits representing the UTF-16 code unit.
    • Autogensyms: To prevent extreme verbosity, __auto__ is escaped as $AUTO_ and numeric components like __18920 are escaped as $18920_. For example, x__18920__auto__ becomes x$18920_$AUTO_.
  3. Understand .child-threading in f/widget

    main

    Inside an f/widget body, expressions are automatically threaded through the .child parameter of the preceding widget. If two expressions are separated by a dotted symbol, that symbol is used for threading instead of .child.

    Example of .child threading:

    (f/widget
      m/Center
      (m/Text "hello"))
    ;; expands to
    (m/Center .child (m/Text "hello"))

    Example of custom threading (e.g., .home, .body, .color):

    (f/widget
      m/MaterialApp
      .home
      m/Scaffold
      .body
      m/Center
      (m/ColoredBox .color m/Colors.pink.shade500)
      (m/Text "Don't stop it now!"))
    (f/widget
      m/MaterialApp
      .home
      m/Scaffold
      .body
      m/Center
      (m/ColoredBox .color m/Colors.pink.shade500
        (m/Text "Don't stop it now!")))
  4. Handle types and nullability in ClojureDart

    main

    ClojureDart uses type hints to manage Dart's strong typing and nullability:

    • Non-nullable: Use ^Type x (e.g., ^String x).
    • Nullable: Use ^Type? x (e.g., ^String? x).
    • Aliased Types: Use the package alias prefix, such as m/ElevatedButton.
    • Parametrized Types: Use the #/(Type ...) syntax for generics. For example, a List<Map> is represented as #/(List Map). You can type hint this as ^#/(List Map) x.
  5. How composite names are constructed

    main

    To represent a list of already munged names as a single unique identifier, CLJD uses Composite Names.

    • The composite name starts with $C$ and ends with $D$.
    • Components within the composite name are separated by $$ when necessary (specifically when two adjacent components are not themselves composite).
  6. How locals are handled to avoid shadowing

    main

    To prevent variable shadowing in the generated Dart code (where Dart does not support Clojure-style shadowing), CLJD assigns unique suffixes to locals.

    Locals are suffixed with $nnn, where nnn is a decimal number. These suffixes are guaranteed to be unique within a top-level form.

  7. Handle non-nullable types in ClojureDart

    main

    In Dart, types are non-nullable by default. When using type hints in ClojureDart, a standard type hint implies the value cannot be nil. To allow nil, you must explicitly append a question mark to the type.

    • ^String x $\rightarrow$ x is a non-nullable String (cannot be nil).
    • ^String? x $\rightarrow$ x is a nullable String (can be nil).
  8. Use Cells for derived state with f/$ and f/<!

    main

    Cells are used to maintain and reuse derived state.

    • f/$ (cache): Creates a cell. Like a spreadsheet cell, it updates its value whenever its dependencies change.
    • f/<! (take): Used to read the value of a cell. It can be used in any function called directly or indirectly from a cell.

    Dependencies can be any watchable, including other cells.

  9. Use notation transcription for Dart generics in ClojureDart

    main

    In ClojureDart, you can use a shorthand notation to specify Dart generic type parameters for any symbol (methods, types, etc.).

    Instead of writing the full metadata, use the #/(symbol type-params) syntax. For example, foo<bar, baz> can be written as #/(foo bar baz). This is equivalent to the metadata ^{:type-params [bar baz]} foo.

    #/(foo bar baz)
  10. Munged name format and invariants

    main

    Every name generated by CLJD must satisfy the following regular expression invariant to ensure it is a valid Dart identifier and follows the internal escaping rules:

    ([a-zA-Z0-9]|\$[a-zA-Z0-9]*_)([a-zA-Z0-9_]|\$[a-zA-Z0-9]*_)*

    Key constraints:

    • No leading underscores.
    • Every dollar sign $ must be followed by alphanumeric characters and must be closed by an underscore _.
  11. Manage resource lifecycles with `:with` in the `widget` macro

    main

    The :with option in the widget macro handles the initialization and disposal of resources (like ScrollController).

    • Initialization: Resources are initialized in initState.
    • Disposal: Resources are automatically discarded in dispose by calling their .dispose method by default.
    • Custom Disposal: To use a different method for cleanup, provide a :dispose key. The resource name is threaded through the disposal form.
    • Intermediate Values: Use :let within :with to create values needed for resource initialization.

    Examples:

    Standard disposal (calls .dispose):

    :with [controller (m/ScrollController.)]

    Custom disposal:

    :with [file (.openSync (io/File "log"))
           :dispose .closeSync]

    Using :let for initialization:

    :with [res1 init1
           :dispose .cancel
           :let [v expr]
           res2 (init2 v)]
  12. Understand Flutter's Three Trees architecture

    main

    When working with Flutter in ClojureDart, it is essential to understand the three underlying trees that manage the UI:

    1. The Widget Tree: The immutable blueprint you write. It describes what the UI should look like. Even StatefulWidgets are immutable; they only describe how to manage state.
    2. The Element Tree: The layer where state lives. There is a 1:1 mapping between widgets and elements. When a widget is updated, the element updates itself to reflect the new configuration.
    3. The Render Object Tree: The heavy lifters that handle layout, painting, and hit testing. This layer interacts directly with the screen.