Thought experiment: Flutter on Go

    Most recently, I discovered Flutter - a new framework from Google for developing cross-platform mobile applications - and even had the opportunity to show the basics of Flutter to a person who had never programmed before. Flutter itself is written in Dart, a language that was born in the Chrome browser and escaped into the console world - and this made me think "hmm, and Flutter could well have been written on Go!".


    Why not? Both Go and Dart are created by Google, both typed compiled languages ​​- turn some events a little differently, Go would be a great candidate for a large-scale project like Flutter. Someone will say - there are no classes, generics and exceptions in Go, so it does not fit.


    So let's imagine that Flutter is already written on Go. How will the code look and in general, will it work out?



    What is wrong with dart?


    I have been following this language since its inception as an alternative to JavaScript in browsers. Dart was built into the Chrome browser for a while and the hope was that it would force out JS. It was really sad to read in March 2015 that Dart support was removed from Chrome .


    Dart himself is great! Well, basically, after JavaScript, any language is great, but after, say, Go, Dart is not so beautiful. but quite ok. It has all imaginable and unimaginable features - classes, generics, exceptions, futures, async-await, event loop, JIT / AOT, garbage collection, function overload - name any known feature from the theory of programming languages ​​and in Dart it will be with a high proportion probabilities. Dart has a special syntax for almost any piece - a special syntax for getters / setters, a special syntax for abbreviated constructors, a special syntax for special syntax, and much more.


    This makes Dart right at a glance familiar to people who have already programmed in any programming language before, and that’s great. But trying to explain all this abundance of special features in the simple "Hello, world" example, I found that this, on the contrary, makes it difficult to master.


    • all the "special" features of the language were confused - "a special method called constructor", "a special syntax for automatic initialization", "a special syntax for named parameters", etc.
    • all the "hidden" confused - "from what import is this function? is it hidden, looking at the code you can't find out", "why is there a constructor in this class, but is there no? he is there, but he is hidden" and so on
    • all the “ambiguous” confused - “so here to create function parameters with or without names?”, “should there be const or final?”, “use normal function syntax here or“ abbreviated with arrow ”and so on.

    In principle, this trinity - “special”, “hidden” and “ambiguous” - not bad captures the essence of what people call “magic” in programming languages. These are features created to simplify the writing of code, but in fact complicating its reading and understanding.


    And this is exactly the area where Go takes a fundamentally different position from other languages, and fiercely holds its defense. Go is a language practically without magic - the amount of "hidden", "special" and "ambiguous" in it is reduced to a minimum. But Go has its drawbacks.


    What is wrong with Go?


    Since we are talking about Flutter, and this is a UI framework, let's consider Go as a tool for describing and working with UI. In general, UI frameworks are a colossal task and almost always require specialized solutions. One of the most frequent approaches in the UI is the creation of DSL - domain-specific languages ​​- implemented in the form of libraries or frameworks, sharpened specifically for the needs of the UI. And most often you can hear the opinion that Go is objectively a bad language for DSL.


    In essence, DSL means creating a new language — terms and verbs — that a developer can operate on. The code on it should clearly describe the main features of the graphical interface and its components, be flexible enough to unleash the designer's imagination, and still be tough enough to restrict it in accordance with certain rules. For example, you should be able to place the buttons on some container, and place the icon in the right place in this button, but the compiler should return an error if you try to insert a button in, say, text.


    Plus, languages ​​for describing UI are often declarative - giving the opportunity to describe the interface in the form of "what I would like to see", and let the framework understand from this what code and how to run.


    Some languages ​​were originally developed with such tasks on the sights, but not Go. It seems that writing Flutter on Go will be the one more task!


    Ode flutter


    If you are not familiar with Flutter yet, then I strongly recommend spending the next weekend watching educational videos or reading tutorials that are many. Because Flutter, without any doubt, overturns the rules of the game in the development of mobile applications. And, quite likely, not only mobile - there are already renderers (in Flutter terms, embedders) in order to run Flutter applications as native dekstop applications , and as web applications .


    It is easy to learn, it is logical, it comes with a huge library of beautiful widgets on Material Design (and not only), it has a great and great community and excellent tuning (if you like the ease of working with go build/run/testGo, then Flutter will get a similar experience).


    A year ago I needed to write a small mobile application (for iOS and Android, of course), and I understood that the complexity of developing a high-quality application for both platforms was too great (the application was not the main task) - I had to outsource and pay for it. In fact, writing a simple, but high-quality application running on all devices was an impossible task, even for a person with almost 20 years of programming experience. And it has always been nonsense for me.


    With Flutter, I rewrote this application for 3 pm, while learning the framework from scratch. If someone had told me that this could be a little earlier, I would not have believed it.


    The last time I saw a similar productivity boost with the discovery of a new technology was 5 years ago when I discovered Go. That moment changed my life.


    So I recommend to start learning Flutter and this tutorial is very good .


    "Hello, World" on Flutter


    When you create a new application through flutter create, you will get such a program with a title, text, a counter and a button that increments the counter.



    I think this is a great example. to write it on our imaginary Flutter on Go. It has almost all the basic concepts of the framework, where you can test the idea. Let's look at the code (this is one file):


    import 'package:flutter/material.dart';
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
      final String title;
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }

    Let's take a look at the code in parts, analyze what and how it falls on Go, and take a look at the various options that we have.


    We translate the code to Go


    The start will be simple and straightforward - import dependencies and launch functions main(). Nothing complicated or interesting here, the change is almost syntactic:


    package hello
    import"github.com/flutter/flutter"funcmain() {
        app := NewApp()
        flutter.Run(app)
    }

    The only difference is that instead of starting up MyApp()- a function that is a constructor, which is a special function that is hidden inside a class called MyApp - we simply call the usual explicit and non-hidden function NewApp(). It does the same thing, but it is much clearer to explain and understand what it is, how it starts and how it works.


    Widget classes


    In Flutter, everything consists of widgets. In the Dart version of Flutter, each widget is implemented as a class that inherits special classes for widgets from Flutter.


    In Go, there are no classes, and, accordingly, a class hierarchy, because the world is not object-oriented, and certainly not hierarchical. For programmers familiar only with a class-oriented OOP model, this may be a revelation, but it really isn't. The world is a giant interlaced graph of concepts, processes, and interactions. It is not perfectly structured, but not chaotic, and an attempt to squeeze it into class hierarchies is the most reliable way to make the code base unreadable and cumbersome - just what most of the code bases are for the time being.



    I really appreciate Go for the fact that its creators have bothered to rethink this ubiquitous class concept and have implemented Go, a much simpler and more powerful concept for the PLO, which, not by chance, turned out to be closer to what the PLO creator, Alan Kay, had in mind .


    In Go, we represent any abstraction in the form of a specific type - structure:


    type MyApp struct {
        // ...
    }

    In the Dart version of Flutter, it MyAppmust inherit StatelessWidgetand override the method build. This is needed to solve two problems:


    1. give our widget ( MyApp) some special properties / methods
    2. enable Flutter to call our code in the build / render process

    I do not know Flutter internals, so suppose that point number 1 is not in question, and we just have to do it. In Go, for such a task there is a single and obvious solution - embedding the types:


    type MyApp struct {
        flutter.Core
        // ...
    }

    This code will add all properties and methods flutter.Coreto our type MyApp. I called him Coreinstead Widget, because, firstly, the type of embedding does not make our MyAppwidget, and, secondly, that the name is used very successfully in the framework, GopherJS Vecty (something like the React, only to Go). I will touch on the topic of similarity Vecty and Flutter a little later.


    The second point - the implementation of the method build()that Flutter can use - is also solved in Go simply and unambiguously. We only need to add a method with a specific signature that satisfies an interface defined somewhere in the library of our fictional Flutter on Go:


    flutter.go:


    type Widget interface {
        Build(ctx BuildContext) Widget
    }

    And now our main.go:


    type MyApp struct {
        flutter.Core
        // ...
    }
    // Build renders the MyApp widget. Implements Widget interface.func(m *MyApp)Build(ctx flutter.BuildContext)flutter.Widget {
        return flutter.MaterialApp()
    }

    We can notice a few differences here:


    • The code is somewhat more verbose  BuildContext, Widgetand MaterialApppoint to the import flutterbefore them.
    • the code is somewhat less unsubstantiated - no words like extends Widgetor@override
    • The method Build()starts with a capital letter, because it means the method’s publicity in Go. In Dart, publicity is determined by whether the name begins with an underscore (_) or not.

    So, to make a widget in our Flutter on Go, we need to embed the type flutter.Coreand implement the interface flutter.Widget. With this sorted out, dig further.


    condition


    That was one of the things that confused me a lot in Flutter. There are two different classes -  StatelessWidgetand StatefulWidget. As for me, a “stateless widget” is the same widget, just without, hmm, data, state — why is there a new class here? But ok, I can live with that.


    But then - more, you can not just inherit another class ( StatefulWidget), but you have to write this kind of magic (IDE will do it for you, but not the essence):


    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
      @override
      Widget build(BuildContext context) {
          return Scaffold()
      }
    }

    Hmm, let's see what is happening here.


    Fundamentally, the task is as follows: add a state to the widget (state), a counter, in our case, and let the Flutter engine know when we changed the state to redraw the widget. This is the real complexity of the problem (essential complexity in terms of Brooks).


    All the rest is additional complexity (accidental complexity). Flutter on Dart comes up with a new class Statethat uses generics and accepts a widget as a type parameter. Further, a class is created _MyHomePageStatethat inherits State виджета MyApp... ok, it can still be somehow digested. But why is the method build()defined by the State class, and not by the class which widget? Brrr ....


    The answer to this question is in the Flutter FAQ and is described in detail here and the brief answer is to avoid a certain class of bugs during inheritance StatefulWidget. In other words, this is a workaround to solve the problem of class-oriented OOP design. Chic.


    How would we do this in Go?


    First, I would personally by all means choose not to create a separate entity for the “state” -  State. After all, we already have a state in each specific type - these are just the fields of the structure. Language has already given us this essence, so to speak. Creating another similar entity will only confuse the programmer.


    The task, of course, is to enable Flutter to react to a change in state (this is the essence of reactive programming, after all). And if we can “ask” the developer to use a special function ( setState()), then similarly we can in return ask to use a special function to speak the engine when it is necessary to redraw and when not. In the end, not all state changes require redrawing, and here we will have even more control:


    type MyHomePage struct {
        flutter.Core
        counter int
    }
    // Build renders the MyHomePage widget. Implements Widget interface.func(m *MyHomePage)Build(ctx flutter.BuildContext)flutter.Widget {
        return flutter.Scaffold()
    }
    // incrementCounter increments widgets's counter by one.func(m *MyHomePage)incrementCounter() {
        m.counter++
        flutter.Rerender(m)
        // or m.Rerender()// or m.NeedsUpdate()
    }

    You can play with different naming options - I like NeedsUpdate()the directness and the fact that this is a widget property (derived from flutter.Core), but the global method flutter.Rerender()also looks good. True, it gives a false sense that the widget is now immediately redrawn, but this is not so - it will be redrawn on the next frame update, and the method call frequency can be much higher than the draw frequency - but our Flutter engine should deal with this.


    But the idea is that we just solved the necessary task without adding:


    • new type
    • generics
    • special rules for reading / writing state
    • special new redefined methods

    Plus, the API is much clearer and clearer - just increment the counter (as you would in any other program) and ask Flutter to redraw the widget. This is something that is not very obvious if we just called setState- which is not just a special function for setting the state, it is a function that returns a function (wtf?), In which we already do something with the state. Again, hidden magic in languages ​​and frameworks makes it very difficult to understand and read code.


    In our case, we solved the same problem, the code is simpler and shorter twice.


    Widgets with status in other widgets


    As a logical continuation of the theme, let's take a look at how the “state widget” is used in another widget in Flutter:


    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Flutter Demo',
            home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
    }

    MyHomePagehere it is a "state widget" (it has a counter), and we create it by calling the constructor MyHomePage()during the build ... Wait, what-what?


    build()called to redraw the widget, quite possibly many times a second. Why do we have to create a widget, especially with the state, every time during the drawing? This makes no sense.


    It turns out that Flutter uses this separation between Widgetand Statein order to hide this initialization / state management from the programmer (more hidden things, more!). It creates a new widget every time, but the state, if it has already been created, finds it automatically and attaches to the widget. This magic happens invisibly and I have no idea how it works - you need to read the code.


    I think this is a real evil in programming - hide and hide from the programmer as much as possible, justifying it with ergonomics. I am sure that the average programmer will not read the Flutter code in order to understand how this magic works, and it is unlikely that they will understand how and what is interconnected.


    For the Go version, I definitely would not want such a hidden witchcraft, and would prefer explicit and visible initialization, even if it means a slightly more unfounded code. Flutter's Dart approach can certainly be implemented, but I love Go for minimizing magic, and I would like to see this philosophy in frameworks. Therefore, my code for widgets with the state in the widget tree I would write like this:


    // MyApp is our top application widget.type MyApp struct {
        flutter.Core
        homePage *MyHomePage
    }
    // NewMyApp instantiates a new MyApp widgetfuncNewMyApp() *MyApp {
        app := &MyApp{}
        app.homePage = &MyHomePage{}
        return app
    }
    // Build renders the MyApp widget. Implements Widget interface.func(m *MyApp)Build(ctx flutter.BuildContext)flutter.Widget {
        return m.homePage
    }
    // MyHomePage is a home page widget.type MyHomePage struct {
        flutter.Core
        counter int
    }
    // Build renders the MyHomePage widget. Implements Widget interface.func(m *MyHomePage)Build(ctx flutter.BuildContext)flutter.Widget {
        return flutter.Scaffold()
    }
    // incrementCounter increments app's counter by one.func(m *MyHomePage)incrementCounter() {
        m.counter++
        flutter.Rerender(m)
    }

    This code loses the version on Dart in that if I want to remove homePagefrom the tree of widgets and replace it with something else, then I will have to remove it in three places, instead of one. But in return, we get a complete picture of what, where and how is happening, where memory is allocated, who causes whom, and so on - the code in the palm of your hand is understandable and easy to read.


    By the way, Flutter still has such a thing as StatefulBuilder , which adds even more magic and allows you to make widgets with state on the fly.


    DSL


    Now we take on the most fun part. How are we going to have a widget tree on Go? We want it to look brief, clean, easy in refactoring and changes, describing the spatial relationships between widgets (widgets that are visually close, should be near and in the description), and, at the same time, flexible enough to describe in it an arbitrary code like event handlers.


    I think the option on Dart is quite beautiful and eloquent:


    return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text('You have pushed the button this many times:'),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
    );

    Each widget has a constructor that takes optional parameters, and what makes the entry really nice here is the named parameters of the functions .


    Named Parameters


    In case you are not familiar with this term, in many languages ​​the parameters of the function are called “positional”, since their position is important for the function:


    Foo(arg1, arg2, arg3)

    , and in the case of named parameters, everything is decided by their name in the call:


    Foo(name: arg1, description: arg2, size: arg3)

    This adds text, but saves clicks and movements through the code, in an attempt to understand what the parameters mean.


    In the case of the widget tree, they play a key role in readability. Compare the same code as above, but without named parameters:


    return Scaffold(
          AppBar(
              Text(widget.title),
          ),
          Center(
            Column(
              MainAxisAlignment.center,
              <Widget>[
                Text('You have pushed the button this many times:'),
                Text(
                  '$_counter',
                  Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          FloatingActionButton(
            _incrementCounter,
            'Increment',
            Icon(Icons.add),
          ),
        );

    Not that. true? Not only is it more difficult to understand (you need to keep in mind what each parameter means and what its type is, and this is a significant cognitive load), but it also does not give us the freedom to choose which parameters we want to transfer. For example, you may not want for your Material application FloatingActionButton, so you just do not specify it in the parameters. Without named parameters, we will have to either force all possible widgets to be specified, or resort to magic with reflection to find out exactly which widgets have been transmitted.


    And since there is no overloading of functions and named parameters in Go, this will not be an easy task for Go.


    Go tree widgets


    Version 1


    Let's take a closer look at the Scaffold object , which is a convenient wrapper for a mobile application. It has several properties - appBar, drawe, home, bottomNavigationBar, floatingActionBar - and these are all widgets. Creating a tree of widgets, we actually need to somehow initialize this object, passing it the above-mentioned widget properties. Well, this is not too different from the usual creation and initialization of objects.


    Let's try the approach "in the forehead":


    return flutter.NewScaffold(
        flutter.NewAppBar(
            flutter.Text("Flutter Go app", nil),
        ),
        nil,
        nil,
        flutter.NewCenter(
            flutter.NewColumn(
                flutter.MainAxisCenterAlignment,
                nil,
                []flutter.Widget{
                    flutter.Text("You have pushed the button this many times:", nil),
                    flutter.Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1),
                },
            ),
        ),
        flutter.FloatingActionButton(
            flutter.NewIcon(icons.Add),
            "Increment",
            m.onPressed,
            nil,
            nil,
        ),
    )

    Not the most beautiful UI code, definitely. The word is fluttereverywhere and asks. to hide it (in fact, I should have called the package material, not flutter, but not the essence), the nameless parameters are completely unclear, and these nils everywhere are clearly confusing.


    Version 2


    Since anyway most of the code will use one type or function from the package flutter, we can use the “dot import” format to import the package into our namespace and, thus, hide the package name:


    import . "github.com/flutter/flutter"

    Now, instead of flutter.Textwe can write simply Text. This is usually a bad practice, but we are working with the framework, and this import will be literally in every line. From my practice, this is exactly the case for which such an import is admissible - for example, as when using a wonderful GoConvey testing framework .


    Let's see what the code will look like:


    return NewScaffold(
        NewAppBar(
            Text("Flutter Go app", nil),
        ),
        nil,
        nil,
        NewCenter(
            NewColumn(
                MainAxisCenterAlignment,
                nil,
                []Widget{
                    Text("You have pushed the button this many times:", nil),
                    Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1),
                },
            ),
        ),
        FloatingActionButton(
            NewIcon(icons.Add),
            "Increment",
            m.onPressed,
            nil,
            nil,
        ),
    )

    Already better, but these nil-s and unnamed parameters ....


    Version 3


    Let's see what the code will look like if we use reflection (the ability to inspect the code while the program is running) to analyze the passed parameters. This approach is used in several early HTTP frameworks on Go ( martini , for example), and is considered a very bad practice - it is insecure, loses the convenience of the type system, is relatively slow and adds magic to the code - but for the sake of experiment you can try:


    return NewScaffold(
        NewAppBar(
            Text("Flutter Go app"),
        ),
        NewCenter(
            NewColumn(
                MainAxisCenterAlignment,
                []Widget{
                    Text("You have pushed the button this many times:"),
                    Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1),
                },
            ),
        ),
        FloatingActionButton(
            NewIcon(icons.Add),
            "Increment",
            m.onPressed,
        ),
    )

    Not bad, and it looks like the original version of Dart, but the lack of named parameters still hurts the eye much.


    Version 4


    Let's step back a bit and ask ourselves what exactly we are trying to do. We do not need to blindly copy the Dart approach (although this will be a nice bonus - less new to teach people already familiar with Flutter on Dart). In essence, we simply create new objects and assign properties to them.


    Maybe try this way?


    scaffold := NewScaffold()
    scaffold.AppBar = NewAppBar(Text("Flutter Go app"))
    column := NewColumn()
    column.MainAxisAlignment = MainAxisCenterAlignment
    counterText := Text(fmt.Sprintf("%d", m.counter))
    counterText.Style = ctx.Theme.textTheme.display1
    column.Children = []Widget{
      Text("You have pushed the button this many times:"),
      counterText,
    }
    center := NewCenter()
    center.Child = column
    scaffold.Home = center
    icon := NewIcon(icons.Add),
    fab := NewFloatingActionButton()
    fab.Icon = icon
    fab.Text = "Increment"
    fab.Handler = m.onPressed
    scaffold.FloatingActionButton = fab
    return scaffold

    Such an approach will work, and even though it solves our problem with “named parameters,” it still confuses the image of the widget tree as a whole. First, the order in which widgets are defined is reversed — the deeper the widget, the earlier it should be created. Secondly, we lost the convenient visual structure of the code, which even with the help of indents quickly made it clear to the depth of the widget in the tree.


    By the way, this approach was used for a very long time in UI frameworks like GTK or Qt . For example, look at the code from the latest Qt 5 documentation :


     QGridLayout *layout = new QGridLayout(this);
        layout->addWidget(new QLabel(tr("Object name:")), 0, 0);
        layout->addWidget(m_objectName, 0, 1);
        layout->addWidget(new QLabel(tr("Location:")), 1, 0);
        m_location->setEditable(false);
        m_location->addItem(tr("Top"));
        m_location->addItem(tr("Left"));
        m_location->addItem(tr("Right"));
        m_location->addItem(tr("Bottom"));
        m_location->addItem(tr("Restore"));
        layout->addWidget(m_location, 1, 1);
        QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
        connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
        connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
        layout->addWidget(buttonBox, 2, 0, 1, 2);
    

    Therefore, I fully admit that for someone this format will be more familiar and more familiar. But, nevertheless, it is hard to argue with the fact that this is not the best way to build a tree of widgets in the form of code.


    Version 5


    Another option that I want to try is to create additional types with parameters to pass to constructor functions. For example:


    funcBuild()Widget {
        return NewScaffold(ScaffoldParams{
            AppBar: NewAppBar(AppBarParams{
                Title: Text(TextParams{
                    Text: "My Home Page",
                }),
            }),
            Body: NewCenter(CenterParams{
                Child: NewColumn(ColumnParams{
                    MainAxisAlignment: MainAxisAlignment.center,
                    Children: []Widget{
                        Text(TextParams{
                            Text: "You have pushed the button this many times:",
                        }),
                        Text(TextParams{
                            Text:  fmt.Sprintf("%d", m.counter),
                            Style: ctx.textTheme.display1,
                        }),
                    },
                }),
            }),
            FloatingActionButton: NewFloatingActionButton(
                FloatingActionButtonParams{
                    OnPressed: m.incrementCounter,
                    Tooltip:   "Increment",
                    Child: NewIcon(IconParams{
                        Icon: Icons.add,
                    }),
                },
            ),
        })
    }

    Wow! This is very good. These types are a ...Paramslittle striking, but still this is much better than the other options so far. This approach, by the way, is quite often used in Go libraries as well, and it works especially well when you have only a couple of structures that need to be created this way.


    In fact, there is a way to remove verbosity ...Params, but this will require a change in language. There is even a proposal (proposal) just under this - "untyped composite literals" . In essence, this means that you can cut FloatingActionButtonParameters{...}up {...}the body of function parameters. Here's what the code will look like:


    funcBuild()Widget {
        return NewScaffold({
            AppBar: NewAppBar({
                Title: Text({
                    Text: "My Home Page",
                }),
            }),
            Body: NewCenter({
                Child: NewColumn({
                    MainAxisAlignment: MainAxisAlignment.center,
                    Children: []Widget{
                        Text({
                            Text: "You have pushed the button this many times:",
                        }),
                        Text({
                            Text:  fmt.Sprintf("%d", m.counter),
                            Style: ctx.textTheme.display1,
                        }),
                    },
                }),
            }),
            FloatingActionButton: NewFloatingActionButton({
                    OnPressed: m.incrementCounter,
                    Tooltip:   "Increment",
                    Child: NewIcon({
                        Icon: Icons.add,
                    }),
                },
            ),
        })
    }

    This is almost a perfect match with the version on Dart! Although it will require the creation of types for each widget.


    Version 6


    Another option I would like to test is chain initialization. I forgot how this pattern is called, but it does not matter, because patterns must be born from code, and not vice versa.


    The idea is that when creating an object, we return it, and right there we can call the setter method, which returns a modified object - and so one after another:


    button := NewButton().
        WithText("Click me").
        WithStyle(MyButtonStyle1)

    or


    button := NewButton().
        Text("Click me").
        Style(MyButtonStyle1)

    Then our code for the Scaffold widget will look like this:


    // Build renders the MyHomePage widget. Implements Widget interface.func(m *MyHomePage)Build(ctx flutter.BuildContext)flutter.Widget {
        return NewScaffold().
            AppBar(NewAppBar().
                Text("Flutter Go app")).
            Child(NewCenter().
                Child(NewColumn().
                    MainAxisAlignment(MainAxisCenterAlignment).
                    Children([]Widget{
                        Text("You have pushed the button this many times:"),
                        Text(fmt.Sprintf("%d", m.counter)).
                            Style(ctx.Theme.textTheme.display1),
                    }))).
            FloatingActionButton(NewFloatingActionButton().
                Icon(NewIcon(icons.Add)).
                Text("Increment").
                Handler(m.onPressed))
    }

    This is also not a very alien concept for Go - many libraries use it for configuration options, for example. It is syntactically somewhat different from the Dart version, but still has all the necessary properties:


    • explicit tree construction
    • named "parameters"
    • indents to help understand the depth of the widget
    • ability to specify handlers and arbitrary code

    Also in all the examples I like using classic naming New...()for constructors — just a function that creates an object. It is much easier to explain to a beginner in programming than to explain constructors - "this is also a function, but her name is the same as that of a class, but you will not see this function, because it is special, and just looking at a function you cannot easily understand - this is a function or constructor object with the same name . "


    Anyway, of all the options, the 5th and 6th seem to me the most attractive.


    The final version of the code


    Putting all the pieces together and trying to write our "hello, world" on an imaginary Flutter on Go:


    main.go


    package hello
    import"github.com/flutter/flutter"funcmain() {
        flutter.Run(NewMyApp())
    }

    app.go:


    package hello
    import . "github.com/flutter/flutter"// MyApp is our top application widget.type MyApp struct {
        Core
        homePage *MyHomePage
    }
    // NewMyApp instantiates a new MyApp widgetfuncNewMyApp() *MyApp {
        app := &MyApp{}
        app.homePage = &MyHomePage{}
        return app
    }
    // Build renders the MyApp widget. Implements Widget interface.func(m *MyApp)Build(ctx BuildContext)Widget {
        return m.homePage
    }

    home_page.go:


    package hello
    import (
        "fmt"
        . "github.com/flutter/flutter"
    )
    // MyHomePage is a home page widget.type MyHomePage struct {
        Core
        counter int
    }
    // Build renders the MyHomePage widget. Implements Widget interface.func(m *MyHomePage)Build(ctx BuildContext)Widget {
        return NewScaffold(ScaffoldParams{
            AppBar: NewAppBar(AppBarParams{
                Title: Text(TextParams{
                    Text: "My Home Page",
                }),
            }),
            Body: NewCenter(CenterParams{
                Child: NewColumn(ColumnParams{
                    MainAxisAlignment: MainAxisAlignment.center,
                    Children: []Widget{
                        Text(TextParams{
                            Text: "You have pushed the button this many times:",
                        }),
                        Text(TextParams{
                            Text:  fmt.Sprintf("%d", m.counter),
                            Style: ctx.textTheme.display1,
                        }),
                    },
                }),
            }),
            FloatingActionButton: NewFloatingActionButton(
                FloatingActionButtonParameters{
                    OnPressed: m.incrementCounter,
                    Tooltip:   "Increment",
                    Child: NewIcon(IconParams{
                        Icon: Icons.add,
                    }),
                },
            ),
        })
    }
    // incrementCounter increments app's counter by one.func(m *MyHomePage)incrementCounter() {
        m.counter++
        flutter.Rerender(m)
    }

    Very nothing!


    Conclusion


    Similarity with Vecty


    I could not help but pay attention to how strongly my decision resembles the way we write code in Vecty . In many ways, they are, in principle, similar, only Vecty displays the result in DOM / CSS / JS, and Flutter carries a powerful rendering and animation engine written from scratch, giving beautiful graphics and cool animation at 120 frames per second. But it seems to me that the design of Vecty is very successful, and my solution for Flutter on Go reminds Vecty for a reason.


    Better understanding of Flutter design


    This thought experiment was interesting in itself - not every day you have to write code on a framework that does not exist. But he also made me dig deeper into Flutter's design and technical documentation to better understand what was behind that hidden magic.


    Disadvantages go


    Answering the question "Can Flutter be implemented on Go?" my answer is definitely yes , but I’m certainly prejudiced, I probably don’t know a lot of restrictions and requirements facing Flutter, and, in general, such questions do not have the “right” answer anyway. I was more interested in the fact that it is in Go that fits well with the current design.


    The experiment demonstrated that the biggest problem with Go was exclusively in syntax . The inability to call a function and pass the parameter names created significant difficulties in the design of a readable and understandable tree of widgets. There are suggestions for adding named parameters to future versions of Go, and it looks like these changes are even backward compatible. But adding them is another thing to learn, another choice before each function, and so on, so the cumulative benefits are not so obvious.


    I have not encountered problems with the lack of generics or exceptions in Go. If you know the best way to achieve the described task in the experiment with the help of generics - please write in the comments with code examples, I will be sincerely interested to hear them.


    Thoughts on the future of Flutter


    My final thoughts will be that Flutter is unusually good, despite all the rumbling that I allowed myself in this article. The "cool / bad" ratio is surprisingly large, and Dart is pretty easy to grasp (at least for people familiar with other programming languages). Given the Dart browser pedigree, I dream that one day all browser engines (although how many of them are left) will go with DartVM instead of V8, and Flutter will be integrated natively - and all Flutter applications will automatically be web applications too.


    The work done on the framework is just astronomical. This is a project of the highest quality and with an excellent and growing community. At a minimum, the number of unnecessarily high-quality materials and tutorials is simply overwhelming as for the framework, version 1.0 of which was released less than a month ago. I hope someday also to contribute to the project.


    For me, this is a game changer, and I hope to master Flutter as much as possible and write mobile applications for myself and for pleasure, for this will no longer be the lot of companies with a staff of mobile developers.


    Even if you have never seen yourself as a mobile UI developer, try Flutter, a breath of fresh air.


    Links



    Also popular now: