Open modulovalue opened 1 month ago
@modulovalue Greetings friend, I am using a translator, I know this context, I wanted to tell you that if you are right, higher order types are needed and today I was putting together a proposal for this, I am going to try to make it minimalist so that it is accessible to everyone. I like your initiative when you use it. I'm going to make reference to this post
@modulovalue Friend, here is my proposal of how I see the introduction of higher order functions. This feature is already supported in the language thanks to the reserved word typedef
. To introduce monads
into the syntax of the Dart language, we must identify the fundamental types that can be used as a monad
The basic types are not candidates to be monads
since they could not be Functors because they are incapable of wrapping a data. This is necessary to define a monad
. The Dart language types that could not be used are:
In the Dart language there are some complex types that are fundamental and if they are compatible with the idea of monad
, they can be the basis for supporting functional programming in the language. First we must explain why they are candidates for monad
.
A type of language that is usable as a monad
is one that allows values to be stored inside, that is, it serves as a wrapper, this is known as a functor. It must also have a series of operators (map, filter, reduce, flatmap) that allow the management of the wrapped value. Among the language types that can meet this characteristic we have:
For these objects to be used as monads
, it is necessary to comply with the singlenton pattern to avoid having repeated instances and behave consistently.
For the DateTime type it is necessary to limit the instantiation of this object, for this we will use the of()
operator which is necessary to construct the object serving as a functor as follows:
void main() {
DateTime date = DateTime.of(2024, 06, 12);
print(date.toString());
DateTime date2 = DateTime.now();
print(date2.toString());
}
This way the DateTime object is usable as a monad
These are the collection base types in the Dart language and are candidates as monads
that must be done in a very specific way. To convert a List, Map or a Set into a monad
, it is necessary to use the of()
operator that allows wrapping values. The difference would be that the resulting collection would be immutable.
It is necessary that the collection must be immutable so that it has consistency and is faithful to the functional programming paradigm. Let's look at some examples:
void main() {
List<int> ex1 = List.of(1, 2, 3, 4, 5, 6);
ex1.forEach((n) => print(n));
Map<String, int> ex1 = Map.of('key1', 11, 'key2', 22, 'key3', 33);
ex2.forEach((key, value) => print(value));
Set<int> ex1 = Set.of(1, 2, 3, 4, 5, 6);
ex1.forEach((n) => print(n));
}
All collections will be immutable and behave like monads
These data types are designed to perform asynchronous operations and in a certain way they are already optimized to function as monads
since they were designed this way from the beginning, the only change that could be made is in the Future
object which has the constructor Future(() => 'hello');
which could be replaced by the of()
operator as follows:
void main() {
Future<String> ex1 = Future.of('hello');
ex1.then((value) => print(value));
Stream s = Stream.value('hello');
s.forEach((value) => print(value));
}
This Optional type is special because it does not currently exist in the Dart language, this type is useful for wrapping null values that can generate an error and helps with null safety, I discuss this in this proposal. The optional type would look like this:
Using optional notation
void main() {
Optional<String> name = null;
// Error: Because you cannot directly access toUpperCase() because you are referring to the Optional wrapper, to access
// toUpperCase() you must extract the String making sure that it is present without any risk
print(name.toUpperCase()); //Access error, The IDE should not suggest this
print(name?.toUpperCase()!); // Correct access, because operators are used that automatically ensure access
print(name?.toUpperCase() ?? 'Not Value'); // Correct access, with default value
}
Using simplified optional notation
void main() {
Optional<String> otherName = null;
String? name = otherName; // Optional<String> equivalent String?
// Error: Because you cannot directly access toUpperCase() because you are referring to the Optional wrapper, to access
// toUpperCase() you must extract the String making sure that it is present without any risk
print(name.toUpperCase()); // Access error, The IDE should not suggest this
print(name?.toUpperCase()!); // Correct access, because operators are used that automatically ensure access
print(name?.toUpperCase() ?? 'Not Value'); // Correct access, with default value
}
Using extended optional notation
void main() {
Optional<String> otherName = null;
String? name = otherName; // Optional<String> equivalent String?
// Error: Because you cannot directly access toUpperCase() because you are referring to the Optional wrapper, to access
// toUpperCase() you must extract the String making sure that it is present without any risk
print(name.toUpperCase()); //Access error, The IDE should not suggest this
print( name.map((str) => str.toUpperCase()).get() ); // Correct access
print( name.map((str) => str.toUpperCase()).orElse('Not Value') ); // Correct access, with default value
}
These would be all my observations. If you have any questions or suggestions, you can comment.
Hello everybody,
I wanted to share a technique that technically shouldn't be possible in Dart, but that is possible in Dart. It is based on a technique that Rúnar Bjarnason introduces in his talk Composable application architecture with reasonably priced monads (in Scala) which he calls "Monad Coproducts", but says that it is based on Data Types à la Carte by Wouter Swiestra.
The interesting part is that, to implement this technique, higher kinded types are needed, which Scala officially supports, but Dart doesn't.
Erik Meijer and Gilad Bracha, both of whom were on the Dart team a long time ago, claim that "Dart can't have proper Monads because expressing the Monad interface would require higher kinded types", see: Erik Meijer and Gilad Bracha: Dart, Monads, Continuations, and More (2012), which, under a broad interpretation of their statement, is not correct, because we can simulate higher kinded types using type defunctionalization.
So, Dart "supports" higher kinded types, which means that Dart also supports expressing a proper Monad interface and so we can implement Monad Coproducts!
What follows is an example implementation of "Monad Coproducts"/"Data Types à la Carte" in Dart. I'd recommend pasting it into an IDE that supports
// region
markers and to collapse them all before exploring it. There's a lot of boilerplate code that obscures the actual intent, and exploring it by regions makes that much easier.Note: it makes heavy use of phantom types. A simpler introduction in a different context can be found here: https://github.com/dart-lang/language/issues/2865
It implements 4 algebras (that is, in this case, sets of operations) called: UI, LOG, CLOUD and IO. They represent distinct concerns. With "Monad Coproducts", we can compose them in one compilation unit while having them exist in separate compilation units without them having to depend on each other, and we don't have to provide an interpretation strategy during composition-time. That is, we can construct a program, and interpret it later, possibly with different interpretation strategies, all without having to sacrifice type safety.
We can observe that:
if (enableLogging) log(...)
) which would obscure the intent of the program.Any comments would be greatly appreciated.