How to call function from hashmap in Scala - list

I'm pretty new to scala and basically I want to have a couple of functions coupled to a string in a hashmap.
However I get an error at subscribers.get(e.key)(e.EventArgs); stating Option[EventArgs => Unit] does not take parameters...
Example code:
object Monitor {
val subscribers = HashMap.empty[String, (EventArgs) => Unit ]
def trigger(e : Event){
subscribers.get(e.key)(e.EventArgs);
}
def subscribe(key: String, e: (EventArgs) => Unit) {
subscribers += key -> e;
}
}

The get method of a Map gives you an Option of the value, not the value. Thus, if the key if found in the map, you get Some(value), if not, you get None. So you need to first "unroll" that option to make sure there is actually a value of a function which you can invoke (call apply on):
def trigger(e: Event): Unit =
subscribers.get(e.key).foreach(_.apply(e.EventArgs))
or
def trigger(e: Event): Unit =
subscribers.get(e.key) match {
case Some(value) => value(e.EventArgs)
case None =>
}
There are many posts around explaining Scala's Option type. For example this one or this one.
Also note Luigi's remark about using an immutable map (the default Map) with a var instead.

Since the get method returns Option, you can use 'map' on that:
subscribers.get(e.key).map(f => f(e.EventArgs))
or even shorter:
subscribers.get(e.key) map (_(e.EventArgs))

get only takes one argument. So subscribers.get(e.key) returns an Option, and you're trying to feed (e.EventArgs) to that Option's apply method (which doesn't exist).
Also, try making the subscribers a var (or choosing a mutable collection type). At the moment you have an immutable collection and an immutable variable, so your map cannot change. A more idiomatic way to declare it would be
var subscribers = Map[String, EventArgs => Unit]()

HashMap.get() in Scala works in a bit different way, than in Java. Instead of returning value itself, get() returns Option. Option is a special type, that can have 2 values - Some(x) and None. In first case it tells "there's some value with such a key in a map". In second case it tells "nope, there's nothing (none) for this key in a map". This is done to force programmers check whether map actually has an object or not and avoid NullPointerException, which appears so frequently in Java code.
So you need something like this:
def trigger(e: Event) {
val value = subscribers.get(e.key)
value match {
case None => throw new Exception("Oops, no such subscriber...")
case Some(f) => f(e.EventArgs)
}
}
You can find more info about Option type and pattern matching in Scala here.

Related

Use a method on a StateNotifier Riverpod for changing a bool [duplicate]

In the context of a Flutter 2.0.5 app whose state I'd like to manage with Riverpod, I thought I can declare a StateNotifierProvider like this:
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateNotifierProvider<CounterStateNotifier>((ref) => CounterStateNotifier());
class CounterStateNotifier extends StateNotifier<int> {
CounterStateNotifier([int count = 0]) : super(count);
void increment() => state++;
}
But Android Studio (and later the Dart compiler as well) complains about the line where I declare the counterProvider variable:
The type 'StateNotifierProvider' is declared with 2 type parameters, but 1 type arguments were given.
Removing the <CounterStateNotifier> type parameter in StateNotifierProvider<CounterStateNotifier> removes the error. However, attempting to read the provider and call its increment method (setting () => context.read(counterProvider).increment() as the onPressed of an ElevatedButton, then pressing the button) gives the following runtime error:
'increment'
method not found
Receiver: 0
Arguments: []
Why is context.read(counterProvider) returning the int state instead of the notifier? And what is the reason behind the type parameter error mentioned in the first part of my question?
I should mention that I'm running my app on the web (with flutter run -d Chrome).
As of Riverpod 0.14.0, State is the default value exposed by StateNotifierProvider.
The syntax for declaring your StateNotifierProvider is now as follows:
final counterProvider = StateNotifierProvider<CounterStateNotifier, int>((ref) => CounterStateNotifier());
Accessing functions now requires adding .notifier (accessing the StateNotifier itself):
context.read(counterProvider.notifier).increment();
And like you've noticed, you now access the state like so:
final count = context.read(counterProvider);
More on the changes here.
You may also use dynamic to accept any type if value for the StateNotifierProvider
final modelProvider =
StateNotifierProvider.autoDispose<ModelClassName, dynamic>(
(ref) => ModelClassName());

object query and remove parentheses in dart, flutter

Hello? I'm building an app using the flutter provider pattern. And I created a process to query the values ​​inside the object. I also have data in my model dart file.
Check the code below.
List<Device> _devices = [
Device(one: 'apple', two: 'iphone'),
Device(one: 'samsung', two: 'galaxy')
];
String Query(String value) {
return _media.where((medium) => medium.one == value)
.map((medium) => (medium.two)).toString();
Query("apple")
So, when I call that function, I expect iphone to be returned. But the results come in (iphne). Actually I know why. After all, the data returned is a List<Device> type. But what I want is to remove the parentheses by returning only the first value in the queried list(meaning only queried list, not the full list). In other words, I want to receive iphone, not (iphone). Currently, I am using substring removing the first and the final word, which seems to have some limitations. Is there any way to remove parentheses in that logic?
You have parentheses because you're calling .toString() on a list:
return _media.where((medium) => medium.one == value)
.map((medium) => (medium.two))
.toString();
To return just .two or the first found object, you just have to do:
return _media.firstWhere(
(medium) => medium.one == value, orElse: () => null)?.two;
That will return the value of .two of the first found object or null if nothing found.
Doc: Iterable.firstWhere()

One-liner to extract domain from email address

How to optionally extract domain from local-part#domain? My attempt is
Try(email.split("#")(1)).toOption
but seems there should be a way without depending on exception handling. Ideally, I am after one-liner.
Not one liner, and only works on 2.13. But this seems very clear to me.
def extractDomain(email: String): Option[String] = email match {
case s"${_}#${domain}" => Some(domain)
case _ => None
}
(Note, if there are more than one # sign, this will just split on the first one).
email.dropWhile(_ != '#').drop(1)
email.split("#").lastOption
These are equivalent ONLY if what's passed is an email address.
If the string passed doesn't include # then lastOption will still return a Some() of the entire string, whereas your solution will return a None.
So if you can trust your input then this answer provides a cleaner approach.
You can use Some(email.split("#")(1)), this will split the String and then wrap in Some, which is instance of Option.
Let me cheat a little: I will prepare separate file Email.scala with extractor:
object Email{
def unapply(mail: String): Option[(String, String)] = {
mail match {
case s"$user#$domain" => Some(user, domain)
case _ => None
}
}
}
and then it can be used with pattern matching:
val Email(_, domain) = "test#domain.com"
Not a one-liner, but I always match on array extractors when I do String.split (pre-2.13), I think it's short enough and reads much better than getting parts by index.
email.split("#", 2) match {
case Array(_, domainPart # _*) => domainPart.headOption
}
limit = 2 makes sure that domainPart has at most 1 element.
Note you don't need a catch-all in this case, since split will always return at least one value in the array (although makes sense to cover it with tests to protect against future changes).

Add element to List that is inside a Map (ImmutableJS)

This is a example of what my state looks like:
state = {
messages: [
{name: 'Bruce', content: 'Hello'},
{name: 'Clark', content: 'World'}
]
}
I am writing a reducer that will take the ADD_MESSAGE action and add a message to the message list. I would like to take into account the case where the 'messages' key is not defined. I am just starting to use ImmutableJS.
This is how I wrote my function:
// This is really bad
const addMessage = (state, message) => {
let mutableState = state.toJS();
if(mutableState.messages){
mutableState.messages.push(message);
}else{
mutableState = {messages: [message]}
}
return fromJS(mutableState);
}
I'm pretty sure there is a better way to do that. It should work whether my state argument is Immutable or not. Any idea? Thank you!
This is a possible implementation:
const addMessage = (state, message) =>
state.update('messages', Immutable.List(),
msgs => msgs.push(Immutable.Map(message)));
The 2nd arg passed to update is the default value if the key doesn't exist, and the 3rd argument is a closure to take the current value for the key (or default value) and perform the required update. The code also converts the message into an Immutable.Map, but you might prefer to use an Immutable.Record type.
See https://facebook.github.io/immutable-js/docs/#/Map/update for more info on update. updateIn is also very useful for updating store state, as are set, merge, setIn and mergeIn.

Query parameters for GET requests using Akka HTTP (formally known as Spray)

One of the features of Akka HTTP (formally known as Spray) is its ability to automagically marshal and unmarshal data back and forth from json into case classes, etc. I've had success at getting this to work well.
At the moment, I am trying to make an HTTP client that performs a GET request with query parameters. The code currently looks like this:
val httpResponse: Future[HttpResponse] =
Http().singleRequest(HttpRequest(
uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/""" +
s"query?seq=${seq}" +
s"&max-mismatches=${maxMismatches}" +
s"&pam-policy=${pamPolicy}"))
Well, that's not so pretty. It would be nice if I could just pass in a case class containing the query parameters, and have Akka HTTP automagically generate the query parameters, kind of like it does for json. (Also, the server side of Akka HTTP has a somewhat elegant way of parsing GET query parameters, so one would think that it would also have a somewhat elegant way to generate them.)
I'd like to do something like the following:
val httpResponse: Future[HttpResponse] =
Http().singleRequest(HttpRequest(
uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/query""",
entity = QueryParams(seq = seq, maxMismatches = maxMismatches, pamPolicy = pamPolicy)))
Only, the above doesn't actually work.
Is what I want doable somehow with Akka HTTP? Or do I just need to do things the old-fashioned way? I.e, generate the query parameters explicitly, as I do in the first code block above.
(I know that if I were to change this from a GET to a POST, I could probably to get it to work more like I would like it to work, since then I could get the contents of the POST request automagically converted from a case class to json, but I don't really wish to do that here.)
You can leverage the Uri class to do what you want. It offers multiple ways to get a set of params into the query string using the withQuery method. For example, you could do something like this:
val params = Map("foo" -> "bar", "hello" -> "world")
HttpRequest(Uri(hostAndPath).withQuery(params))
Or
HttpRequest(Uri(hostAndPath).withQuery(("foo" -> "bar"), ("hello" -> "world")))
Obviously this could be done by altering the extending the capability of Akka HTTP, but for what you need (just a tidier way to build the query string), you could do it with some scala fun:
type QueryParams = Map[String, String]
object QueryParams {
def apply(tuples: (String, String)*): QueryParams = Map(tuples:_*)
}
implicit class QueryParamExtensions(q: QueryParams) {
def toQueryString = "?"+q.map{
case (key,value) => s"$key=$value" //Need to do URL escaping here?
}.mkString("&")
}
implicit class StringQueryExtensions(url: String) {
def withParams(q: QueryParams) =
url + q.toQueryString
}
val params = QueryParams(
"abc" -> "def",
"xyz" -> "qrs"
)
params.toQueryString // gives ?abc=def&xyz=qrs
"http://www.google.com".withParams(params) // gives http://www.google.com?abc=def&xyz=qrs