-
Notifications
You must be signed in to change notification settings - Fork 721
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
Read networkId from environment #4666
Conversation
e9cf022
to
03d1119
Compare
03d1119
to
4b9a6c5
Compare
For support please reach out to our support team https://iohk.zendesk.com/hc/en-us/requests/new |
7416ee9
to
771c004
Compare
@FonziFonzarelli Open an issue or go the the link @CarlosLopezDeLara pasted above. Do not hijack open PRs, you will not receive help. |
99a54fa
to
14c9fe0
Compare
14c9fe0
to
837f998
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, a few comments.
displayErrorText :: Error e => e -> Text | ||
displayErrorText = Text.pack . displayError | ||
|
||
rescue :: Functor m => m (Either x a) -> (x -> y) -> ExceptT y m a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rescue
is a weird name for this. What about firstNewExceptT
? That's a bit more indicative and you should consider swapping around the parameters if it doesn't make its usage awkward i.e firstNewExceptT :: Functor m => (x -> y) -> m (Either x a) -> ExceptT y m a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is called rescue
in Ruby.
I like rescue
much better than firstNewExceptT
.
Also it is more readable to add " `rescue` ErrorTag" trailing at the end of the line.
Then the good- case comes first and error handling is less distracting when reading the code.
(The rescue
pattern exists in several other languages..)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rescue
is still vague IMO and because it exists in other languages doesn't necessarily mean it's a good name. What about liftMapError
? Let's get @newhoggy 's input on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me rescue
is much easier to read and type than liftMapError
. 7 vs 12 char and no need for Caps.
Some Haskell guys would even introduce a &%>
style operator for that.
For and old coder, who needs thick glasses and uses ever bigger font sizes readability is important.
Some IDEs even show the type when hovering the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is a bit tangential but relevant.
I had something thoughts about error handling in general.
A function like rescue
seems generic - but then if it is generic it should go in some generic error handling module, but when we do that we might eventually encounter a problem:
A function like rescue
seems fine by itself, but in practise, we might need a family of functions and would then have trouble naming them. For example:
rescue :: Functor m => m (Either x a) -> (x -> y) -> ExceptT y m a
rescue :: Functor m => Either x a -> (x -> y) -> ExceptT y m a
rescue :: Functor m => ExceptT x m a -> (x -> y) -> ExceptT y m a
I was unhappy with my earlier PR that introduced onNothingThrow
for this reason. It was defined like this:
onNothingThrow :: Monad m => e -> Maybe a -> ExceptT e m a
But then what about all these other possibilities?
onNothingThrow :: Monad m => e -> Maybe a -> ExceptT e m a
onNothingThrow :: Monad m => e -> m (Maybe a) -> ExceptT e m a
onNothingThrow :: Monad m => e -> ExceptT e m (Maybe a) -> ExceptT e m a
I decided I only need the last one because everything else can be lifted to it.
See this PR: #4807
Interested in your thoughts.
(Note, I'm intending to restructure oops
to use more readable English names and avoid the explosion of permutations in a similar way)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind I closed the PR and favouring hoistMaybe
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we don't define rescue
and should instead inline it:
getNetworkId :: NetworkIdArg -> (EnvNetworkIdError -> e) -> ExceptT e IO NetworkId
getNetworkId arg errorTag
= firstExceptT errorTag $ ExceptT $ getNetworkId' arg
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with defining it inline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is not one use site of the rescue
function but there are about ca 100 possible use sites.
This is the 101 of programming: if there are multiple repetitions of the same pattern one defines a function.
Otherwise the code becomes extremely ugly and hard to maintain.
(As is already the case.)
If there is a need for a family of functions which all do something similar like lifting and error handling
then that is indeed a case for a type class.
It makes no sense at all to just refuse to add any kind of abstraction, just because people cannot agree on a common name for a function.
Once an abstraction is in place, it is very easy to rename the function.
It is better to start with a simple abstraction and think about a type classs later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had something thoughts about error handling in general.
A function like rescue seems generic - but then if it is generic it should go in some generic error handling
module, but when we do that we might eventually encounter a problem:
A function like rescue seems fine by itself, but in practise, we might need a family of functions and would then
have trouble naming them. For example:
This kind of argument is well know and it goes by the phrase:
"Perfect is the enemy of good"
As stated earlier, there are lots and lots of places in the code that could be simplified by using rescue
.
If there is need for a more elegant and more generic solution in the future
then replacing the repetitive pattern with a function like rescue
now is still a very good step in that direction.
NetworkIdFromCLI x -> return $ Right x | ||
NetworkIdFromEnv -> readEnvNetworkId | ||
|
||
getNetworkId :: NetworkIdArg -> (EnvNetworkIdError -> e) -> ExceptT e IO NetworkId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should export getNetworkId'
as getNetworkId
. Leave it as a vanilla function with no ExceptT
usage. Then we should rename getNetworkId
to firstGetNetworkId
or something like that to make it more obvious that we are transforming the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Primed names can be problematic in general, but here is no danger of ever using getNetworkId'
instead of getNetworkId
because they have different types.
getNetworkId
is the primary function with 47 call sites.
getNetworkId'
an auxiliary function with 7 call sites.
The simple name should be used for the primary function with more call sites
and the more complex name for the auxiliary function with fewer call sites.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getNetworkId
is specialized for our use case in the cli. This should be renamed to indicate we are mapping over the error hence the name firstGetNetworkId
. These functions are likely to end up in cardano-api
, which is why I am suggesting we rename getNetworkId' :: NetworkIdArg -> IO (Either EnvNetworkIdError NetworkId)
to getNetworkId
. We should not impose our specific cli design decisions (ExceptT
) on users.
-- | ||
data SignForNetworkId | ||
= SignForNetworkId !NetworkId | ||
| TakeNetworkIdFromKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a new sum type and new parsers why not just use Maybe NetworkId
like before? SignForNetworkId
doesn't really bring much value over Maybe NetworkId
. Also the constructor TakeNetworkIdFromKey
is misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you suggest is the situation as it is.
And this situation is very unsatisfactory because at the moment Maybe NetworkId
is used with two different meanings and two different interpretations in the (existing) code.
Some of the commands resolve Maybe NetworkId
using the network ID from the environment or the flag.
But there is also the one that takes the network ID from the key in the Nothing
case.
The best way to handle that is to create a new data type in Haskell.
That is very cheap and helps to cleanup stuff a lot.
It is a common pattern to use
data MyFeature = HaveFeature | DontHaveFeature
instead of
type MyFeature = Bool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but I doubt it's worth further increasing the complexity + size of the codebase to differentiate between these two cases. We already have numerous data definitions in the cli and I don't want to introduce anymore unless the benefit is significant. I don't see that being the case here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is much more readable, adds very little overhead and helps to avoid bugs.
When adding support for networkId from env it was necessary to differentiate between these two cases.
if done right (!) it can even help to reduce code complexity and code size.
Also can help to get better help messages in the CLI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is an example.
Maybe NetworkId
is defaulted to Mainnet
right in the middle of some code.
With a proper datatype it would be easier to spot and avoid unwanted defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way to fix that is to change the function type signature from:
runTxCalculateMinFee
:: TxBodyFile
-> Maybe NetworkId
to
runTxCalculateMinFee
:: TxBodyFile
-> NetworkId
Which eliminates the need to add another data definition here because we don't want defaults.
837f998
to
a8c491d
Compare
a8c491d
to
6a5fe53
Compare
-- | ||
data SignForNetworkId | ||
= SignForNetworkId !NetworkId | ||
| TakeNetworkIdFromKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but I doubt it's worth further increasing the complexity + size of the codebase to differentiate between these two cases. We already have numerous data definitions in the cli and I don't want to introduce anymore unless the benefit is significant. I don't see that being the case here.
displayErrorText :: Error e => e -> Text | ||
displayErrorText = Text.pack . displayError | ||
|
||
rescue :: Functor m => m (Either x a) -> (x -> y) -> ExceptT y m a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rescue
is still vague IMO and because it exists in other languages doesn't necessarily mean it's a good name. What about liftMapError
? Let's get @newhoggy 's input on this
NetworkIdFromCLI x -> return $ Right x | ||
NetworkIdFromEnv -> readEnvNetworkId | ||
|
||
getNetworkId :: NetworkIdArg -> (EnvNetworkIdError -> e) -> ExceptT e IO NetworkId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getNetworkId
is specialized for our use case in the cli. This should be renamed to indicate we are mapping over the error hence the name firstGetNetworkId
. These functions are likely to end up in cardano-api
, which is why I am suggesting we rename getNetworkId' :: NetworkIdArg -> IO (Either EnvNetworkIdError NetworkId)
to getNetworkId
. We should not impose our specific cli design decisions (ExceptT
) on users.
Two formats are supported * CARDANO_NODE_NETWORK_ID=mainnet * CARDANO_NODE_NETWORK_ID=testnet-xx where xx parses as Word32
6a5fe53
to
41c9003
Compare
Implemented in #5119 so closing this PR |
Read network ID from environment.
Two formats are supported
CARDANO_NODE_NETWORK_ID=mainnet
CARDANO_NODE_NETWORK_ID=testnet-xx
where xx parses as Word32