-
Notifications
You must be signed in to change notification settings - Fork 8
New library: Hoa\Option #56
Comments
I like the idea, it's really close to immutable ValueObject concept. I will think a bit about the implementation and give you a feedback. |
@Pierozi Thanks for the feedback. What about the concept? Do you think it will improve safety? What about public API: Do you think users will know how to deal with |
I really like everything you propose except for one thing: I don't think integrating the iterator in Option is good. We want to deal with the presence (or absence) of valie not how we want to iterate over it. To me, constructing thz iterator, if necessary, should be delegated to the option consumer. Moreover how would you choose which kind of iterator to produce? |
@jubianchi Exactly. We agree on this point. This is a feature we could introduce in the future with a new RFC I guess. |
I got a very concrete example today. A collection owns several products, but, new thing, a product can have no collection. So $title = $product->hasCollection() ? $product->collection()->title() : null;
Or, with an $title = $product->collection()->map(function (Collection $collection) { return $collection->title(); });
If https://wiki.php.net/rfc/arrow_functions or another RFC of the same kind gets accepted, we could write it like this: $title = $product->collection()->map(|Collection $collection| { return $collection->title(); }); If we omit the type-hint for $title = $product->collection()->map(|$collection| { return $collection->title }); Much better from my point of view! The |
@Hywan great example! 👍 With short arrow function syntax, the code would be very clear : $title = $product->collection()->map($collection => $collection->title); Too bad we don't have them in PHP... But because PHP will always allow us (developers) to do weird/wrong things, I would keep the type-hint in the closure to make everything even safer: $title = $product->collection()->map(function (Collection $collection) { return $collection->title(); }); IMHO this is the way to go. Properties are not strictly typed so anyone extending the |
LGTM 👍 |
My only concern of this design is to lead us to have Option object everywhere who embed the real value. I would prefer to go only with an Interface free to implement instead of generic Option object. |
@Pierozi The goal is to avoid having null value I think. But yes, it will hide the type of the output, until PHP got generic (so |
I'm with @Pierozi on this one, I would love to have Option in php but not having generics is a major drawback:
the only way (as awful as it might be) to mitigate this I can think of would be to autogenerate classes based on the name, eg It might help catch error earlier but I don't think this can help IDEs or static analysers (might be worst in fact 😕) With the existing generics RFC, if namespace Hoa;
class Option<T = mixed> {
public function unwrapOr(T $fallback): T {/* ... */}
} |
@Hywan as quick example, i've something like this in my mind. |
@nikic Hello :-). Do you think we have a chance to have generics inside PHP soon? Thanks! |
@Pierozi The problem with an interface is that we have to implement all methods, or provides a trait… hmm, with a trait we will reduce the amount of bugs, that could be a nice alternative, but we still can't write:
But OK, if Thought? |
Trait only work when you know the definition of object, in this case the property value, but that's depend of the object. And yes i recognize Interface are not very helpful just help to provide standardization of the Some/None in this case. php limitation :/ |
@Pierozi You can do this: class MyObject implements Option
{
use OptionTrait;
} And you have an option 😄. |
Not really because your trait define arbitrary a property to use and the assertions to valid a data. In my point of view, trait should only be a pure function, not deal with object property. |
IMHO,
So, an example to show my POV: function foo(int a) : Option/*<int>*/
{
if ($a > 0) return Option::some($a);
return Option::None;
}
function bar(int $a = null) : int
{
if (null === $a) return 0;
return $a * 2;
}
$a = rand(0, PHP_INT_MAX); // $a is an int
$b = foo($a); // $b is an Option<int>
$c = bar($a->unwrapOr(null)); Using this is, again IMHO, the only way of ensuring we get the right arguments at the right place. Without generics, if function bar(Option $a) {
$aa = $a->unwrapOr(0);
if (is_int($a) === false) {
throw new \InvalidArgumentException();
}
return $aa;
} Here we are doing half the work of PHP, i.e checking arguments type. Not mentioning that IDE will never help us in identifying argument requirements because they will all expect an Another question : how to deal with optional arguments? Using an $c = bar(Option::None); To sum-up my POV: @Hywan you said
If this ever happen, we will have to drop support for several PHP version to be able to use it fully. My 2 💵 😉 |
I agree with @jubianchi. We should not use <?php
function f(int $x): int
{
return $x << 2;
}
f(2); // 4
f(null); // fails because null is not an integer
f(); // fails because an argument is missing This illustrates my claim. If an argument does not accept In Rust for instance, this is very rare to accept The power of $product->collection()->map($c => $c->title())->map($t => $t->translate('fr_FR')); instead of: $translation = null;
$collection = $product->collection();
if (null !== $collection) {
$title = $collection->title();
if (null !== $title) {
$translation = $title->translate('fr_FR');
}
} |
Now, the question remains: How to be compatible with IDE? I don't use an IDE, so I can't answer this answer :-(. |
@Hywan unfortunately, IDEs will probably have problem inferring option values. But is it really a big problem ? Today, with PHP and IDE, we can't be sure and get hints on collections for example: a function expects a collection (let's say a The problem exists and we can't deal with it today... no generics support in the language and no generics support in PHPDoc annotation. I just tested with PHPStorm (2016.2): <?php
class Option {}
/**
* @return Option<int>
*/
function foo(Option $a) : Option
{
return $a;
}
$b = foo(new Option); PHPStorm tells me IMHO, IDE are a "fake" problem: we can do everything we want to make the code self-documented and provide IDE with what they need, developers will always have to read the library source code at some point to know what they should provide or what they should expect. |
I wouldn't mind about IDEs, it might be a bit more annoying for static analyser (eg: scrutinizer, phan). but maybe they can be taught to read generic types in docblock |
So I guess we can move this RFC as ready? |
Hello fellow @hoaproject/hoackers! I have created the new library If no feedback before the 21st August 2017, I will release the 1.0 version. Take care of the documentation, the PHP 7.1 requirement, the build matrix on Travis etc. please. |
nice ☺ just a one things after reading the README is there a difference between |
note: I half expected something using along the line of Finite-State Machine as a Type System illustrated with a store product with an |
You're correct, I propose to keep the |
About the article you mentionned, this is not applicable here because an option is a monad, all the methods return a new option, there is no particular state (with a PHP implementation). |
https://github.com/hoaproject/Option has been released. I had to update |
Hello @hoaproject/hoackers,
I am proposing a new RFC to introduce a new library, called
Hoa\Option
.Introduction
The
Option
sum type is very powerful. In some languages, like Rust or Haskell, it replaces thenull
value, and thus it increases the safety of the language itself.The definition of
Option
is the following:You must read it like: Either we have some value
T
, or we have nothing, akanull
.This new library will be very helpful if designed correctly. The PHP type system is yet poor and fragile, but we can still provide more safety with such a type. For instance, in the
Hoa\Socket
library, we have to deal with a lot ofnull
value, in expected or unexpected locations. Having anOption
in PHP 7 (so with declared types) would provide a better safety. How? First by having a real type forcing us to deal with unexpected situation, and second by automatically panicing/throwing aRuntimeException
. It can be caught elegantly if it happens, unlike anull
value that will just be silent until a bad usage of it.The pros of an
Option
in PHP is the API it provides to deal withnull
value. Not only to replacenull
andis_null()
, but to really chain operations on it without taking care if the value isnull
or not.Design of the API
In PHP, we do not have the same expressiveness that we can have in Rust, Haskell, or Caml for instance. Nevertheless, we have powerful mechanisms like autoloaders, import etc., to have our own approach.
Having the value wrapped in a type (
Option
) and inaccessible from the outside will force us to write the code differently. We will no longer pass values directly, but we will extract them from the option before passing them to another function. So for instance, let's pretendf
is a function that can return a nullable value, then this actual code:could be rewritten as:
What to do if
$x->isSome()
isfalse
? We should initialize$y
tonull
? It sounds like we are postponing the problem. TheOption::expect
API is helpful in this particular case: Unwrap the value is some, else “panic” i.e. throw aRuntimeException
for instance, thus:Write code differently
This library will definitively force us to write our code differently. We will use less variables, more edge cases/error cases will be handled correctly and very well identified.
Proposed API
I really like the API from Rust:
std::option::Option
:isSome(): bool
, check if there is some value,isNone(): bool
, check if there is none,expect(string $message): mixed
, unwrap the value if some, throw aRuntimeException
exception with the message$message
else,unwrap(): mixed
, return the value if some, throw aRuntimeException
with a default message else,unwrapOr(mixed $default): mixed
, return the value if some,$default
else,unwrapOrElse(callable $default): mixed
, return the value if some, or return the result from the callable$default
else,map(callable $mapper): Option
, map the value into anotherOption
with$mapper
,mapOr(callable $mapper, mixed $default): Option
, map the value into anotherOption
if some with$mapper
, build a newOption
with the value$default
else,mapOrElse(callable $mapper, callable $default): Option
, same as before but the default value is returned from the$default
function,,iterator(): Iterator
, return an iterator over the value if some, panick elseand(Option $operand): mixed
, return$operand
if$this
is some,none
else,andThen(…)
, to be defined likeflat
, must be discussed,or(Option $operand): mixed
, returnoperand
if$this
or$operand
is some,none
else,orElse(…)
, same asandThen(…)
.Cloning
Whether
Option
will be clonable or not must be strictly defined.Serializing
Whether
Option
will be serializable or not must strictly defined.Iterating
It might be possible to iterate over a value. It will introduce a strong dependency to
Hoa\Iterator
. It would be possible to compute a simple\Iterator
object, but we will loose some benefits when writing code (need to instanciate newHoa\Iterator
objects manually for instance, less clear code). Maybe we should do nothing for a first step.Magic methods
__isset(): bool
could be an alias toisSome(): bool
.Mutability
Option
will be immutable. No possibility to get a mutable reference with the current API. Could be introduce later with smart mechanisms.Instanciating
The most powerful API I have in mind yet is the following:
We can automatically import
some
andnone
as function alias to respectivelyOption::some
andOption::none
in the global scope, so:Just writing
none
instead ofnone()
implies thatnone
is a constant, so all instances will be identical. Since an option is immutable, it can make sense. It will save some memory allocations, which will be good.Nullable type
PHP has nullable type, like
?Foo
, which means either thenull
value, or a value of kindFoo
. This is somewhat similar toOption<Foo>
, but with no API, exceptis_null(mixed $value): bool
to see if$value
is null or not, which is equivalent toOption::isSome(): bool
. However,Option
has a much richer API, withunwrap
,and
,or
,except
,map
etc. This is “null with superpowers”. Unfortunately,?Foo
is a type, whileOption<Foo>
cannot be expressed in PHP yet, so we could not have such a type.Concrete implementation
Not completely designed yet. First idea: Having an
Option
class, with 2 interfaces:None
andSome
. That would allow to write such code:will be strictly equivalent to:
Note we could not change the implemented interface on-the-fly, except with
Reflection
, but it comes with a cost. This might not be a good idea.Second idea, just a single
Option
class, with the mentionned API. It must have as few attributes as possible to reduce the size ofOption
. This library will add a runtime cost, it must be as much insignificant as possible.Conclusion
What do you think? I assume you will have a lot of questions about this RFC. I cannot address all of them ahead of time. I could add a FAQ, but I prefer to get fresh and unoriented feedbacks first. A FAQ can be added in the future.
Thanks!
The text was updated successfully, but these errors were encountered: