-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Problem: We can not chain top level functions on ONE like we can chain on MANY #166
Comments
Would these functions not better live as methods inside With the suggested If you want to compose free functions today, you could btw do something like this: C Function(A) compose<A, B, C>(B Function(A) f, C Function(B) g) => (a) => g(f(a));
T Function(T) composeAll<T>(List<T Function(T)> fn) => fn.reduce(compose);
String emphasizeMessage(String message) => composeAll([
capitalize,
greet,
exclaim,
smile,
(String it) => beg(it, times: 3),
(String it) => cheer(it, times: 2),
end
])(message); |
Let is the following: inline fun <T, R> T.let(transform: (T) -> R) = transform(this) So it lets you run a block of code that will map the current object to something else, then you can continue chaining at the end of the block. |
Exactly, hence my comment about You could certainly do something like Consider this example: abstract class Base {
R map<R, T>(R Function(T) transform) {
if (this is T) {
return transform(this as T);
} else {
return null;
}
}
}
class A extends Base {}
class B extends Base {}
B mapAtoB(A a) => B();
A mapBtoA(B a) => A();
main() {
final a = A();
final b = a.map(mapAtoB);
assert(a != null); // true as expected
assert(b != null); // true as expected
final whatsthis = a.map(mapBtoA); // type mismatch, but compiles just fine
assert(whatsthis != null); // assertion failure
} It "works", but I think the bar should be much higher for such a language feature: Not only is the type mismatch not obvious because it compiles without warnings, but also when you trigger autocorrect after typing in Now maybe there is another way and I'm just not experienced enough in Dart, in that case I would love to stand corrected! |
There is one more option from functional programming land: you can define a simple box functor that will allow you to chain functions in a type-safe manner: class Box<T> {
const Box(this.value);
final T value;
Box<S> map<S>(S Function(T) f) => Box(f(this.value));
}
String emphasizeMessage(String message) => Box(message)
.map(capitalize)
.map(greet)
.map(exclaim)
.map(smile)
.map((String it) => beg(it, times: 3))
.map((String it) => cheer(it, times: 2))
.map(end)
.value;
void main() {
print(emphasizeMessage('hello'));
} That's why it works for arrays: they are functors. An arbitrary class isn't, but you can always simply wrap it inside one. |
...maybe all we need is the |
I'd disagree that function composition or functors/monads are inelegant, in fact I think they are the correct solution here. But the question still stands whether this is considered "good enough": it's not uncommon to want such a thing, but this requires knowledge of more advanced functional programming techniques. I do not think that The way I see it, there are two impediments:
Personally, and independent of the original problem, I'm very much in favor of having 1) in Dart, and I'd also be interested in 2). I'm coming from Swift, and there you also can't extend generic types, but you can achieve something similar. For those interested, here's a very simple Swift implementation: protocol Mappable {}
extension Mappable {
func map<R>(_ f: (Self) -> R) -> R { return f(self) }
}
// Retrofit existing types (String, Int etc. are structs, so can't add conformity via a base class)
extension String: Mappable {}
extension Int: Mappable {}
func greet(name: String) -> String { return "Hello \(name)" }
func addTwo(number: Int) -> Int { return number + 2 }
func stringify(number: Int) -> String { return String(number) }
"Bob".map(greet) // "Hello Bob"
42.map(addTwo) // 44
1000.map(stringify).map(greet) // "Hello 1000"
"Bob".map(addTwo) // error: cannot convert value of type '(Int) -> Int' to expected argument type '(String) -> _' |
I think the idea of adding a method to But let's pretend for a minute that we would actually be able to add the following method (and let's pretend that we can call it // Yep, it's _that_ class Object.
class Object {
... // Existing stuff.
X let<X>(X Function(This) f) => f(this);
} This is similar to the extension method (as in Kotlin, and as it might be expressed in an extension of Dart where some sort of extension methods are supported), and it will allow the same level of static typing. For instance, the following would work just fine: String emphasizeMessage(String message) => message
.let(capitalize)
.let(greet)
.let(exclaim)
.let(smile)
.let((it) => beg(it, times: 3))
.let((it) => cheer(it, times: 2))
.let(end); At each step, type inference will ensure that the type argument int addTwo(int i) => i + 2;
var _ = 'Another string'.let(addTwo); // Compile-time error. The error arises because the argument to On top of this, we'd also get the ability to use An aside: Note that the self-type class A {
X let<X>(X Function(A) f) => f(this); // Add this to emulate `This` in the signature.
... // Rest of A.
} Obviously, if we really want this feature and we don't want to introduce a full-fledged self-type then we can let the compiler generate such methods. Similarly, if we want to avoid the breakage then we could call it |
I like abstract class num {
// rest
bool operator ==(This other); // <, <=, >, >=
This operator +(This other); // -, *
This abs(); // etc.
} |
Today, you can just do: String emphasizeMessage(String message) => [message]
.map(capitalize)
.map(greet)
.map(exclaim)
.map(smile)
.map((it) => beg(it, times: 3))
.map((it) => cheer(it, times: 2))
.map(end)
.first; That doesn't seem too bad to me. |
I think that if you are going to wrap it in an array, that the proposed |
This issue is solved by extension methods. |
I have the following top level functions:
Say that I now want to perform those functions on "many" strings.
With that I mean, on a variable that implements
Iterable<String>
.In that case I am allowed to "chain" those functions in an easy to read and easy to write way.
In this way the chronological order of the code is preserved.
However, say now that I would like to do the exact same thing on "one" string.
Now the most trivial implementation becomes hard to read and even harder to write.
There all kind of ways to make this a little better to read. But all of them have disadvantages, and none is so elegant as when working with
Iterable
s.There are some solutions proposed for this. For example, the pipeline operator (#43) and extension methods (#41). I think there is an other solution which is more in line with the Dart language than the pipeline operator, and could exist next to extension methods (or even be implemented like an extension method).
This is inspired by Kotlin's let extension method. For Dart a good name may be the
do
method. It would look like this:I will also open another specific feature issue for this
do
orlet
method that could be implemented by top level typeObject
.The text was updated successfully, but these errors were encountered: