Kotlin: mutableMap doesn't recognise put() - dictionary

This is part of a recipe app. I am in the Recipe class which I have written. Ingredient is another class I have written. The idea is to store the ingredient and its amount (as an Int) in a map.
In the class body I have declared the map as:
var ingredients: Map<Ingredient, Int>
And in the init{} body:
ingredients = mutableMapOf<Ingredient, Int>()
Here is the problem - this function is to add an ingredient. It updates the quantity if the ingredient is already in the map. the put()method should do this but Android Studio is turning it red, and when I mouse over the word 'put' it says 'Unresolved reference: put'. The plus symbol also has a red underline. I thought this was a basic part of the mutable map. Where have I gone wrong? (Don't worry - there will be an 'else' part!)
fun addIngredientAndAmount(ingredient: Ingredient, quantity: Int) {
if (ingredients.containsKey(ingredient)) {
val oldQuantity = ingredients[ingredient]
ingredients.put(ingredient, oldQuantity + quantity)
}
}

Your ingredients is declared as Map, but Map represents a read-only interface so it has no put which is a function of MutableMap. It doesn't matter if you initialize it as MutableMap, because the compiler checks the type you specified for the variable.
You should declare it as:
var ingredients: MutableMap<Ingredient, Int>
You can also initialize it in place instead of using the init block:
var ingredients: MutableMap<Ingredient, Int> = mutableMapOf<Ingredient, Int>()
If you do so you can also avoid stating the type explicitly, as the compiler will automatically infer it.
var ingredients = mutableMapOf<Ingredient, Int>()
or
var ingredients: MutableMap<Ingredient, Int> = mutableMapOf()

Related

How to use flatbuffers `key` attribute to simulate HashMap with flatbuffers in Rust?

I need to create a string to string map with flatbuffers in Rust similar to:
pub struct HashMapIndex {
keyword_by_filter: HashMap<String, String> // filter text -> keyword
}
I've found some key flatbuffers attribute that can be put on array item, but it seems to be neither documented nor shown in samples:
Can be used for in-place binary search
table KeywordToFilters {
filter_text: string (key);
keyword: string;
}
table Index {
map: [KeywordToFilters];
}
How can i use it (get value by key)? The problem is that no map-like methods are generated - it's just a vector:
pub map: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a , flatbuffers::ForwardsUOffset<KeywordToFilters<'a >>>>>,
Is there more efficient way to have a map with flatbuffers in Rust?
PS. flatbuffers crate version is 0.8.3

Make flow assume that a particular key does exist in a Map

Due to the way you're iterating over something, you may know that a key always exists in a Map but flow may not know.
Currently, how I'm getting rid of it is to check if the result returned from get() is not undefined.
I don't want to add those spurious checks though, and instead I want to tell the typesystem that it can assume that that key exists. Telling this to the typesystem also conveys more information (this key definitely exists) about the program to a future reader than a spurious check (this key may or may not exist, and if doesn't exist then we're ignoring that case)
So I want to be able to do the following by preferably adding something on or before line 3 instead of an if check.
1. let score:Map<string, number> = new Map()
2. initialize(score); // some magic initilization of the map that flow doesn't understand
3. score.get("hello");
I wouldn't recommend doing this but you could extend the Map class to define your own class that defines get,
declare class MyMap<K, V> extends Map<K,V> {
get(key: 'hello'): V;
get(key: K): V | void;
}
so that (new myMap()).get('hello') has type V instead of V | void. For example,
const myMap: MyMap<string, number> = new MyMap();
const n: number = myMap.get('hello');
const o: number = myMap.get('not hello'); // error
const p: number | void = myMap.get('not hello');
Try Flow
You'll need to change your code so that initialize returns a MyMap instead of modifying the passed in Map. Note that although this will pass Flow, it may still break during running. As you can see in the example above, even though I have created a new MyMap(), I have not added the key 'hello' and so n isn't really a number.

How to create a Flow Union runtime refinement without embedding literals

Hello kind Stackoverflow folks,
I'm trying to create a function to guard off code from being executed at run-time with an incorrect Flow type present.
My understanding is that the way to do this at run-time is by refining, or checking, that the type matches what is required and using Flow to keep an eye that no cases are missed along the way.
A simple case is where I have a string input that I would like to confirm matches to a enum/Union type. I have this working as I would expect with literals e.g.
/* #flow */
type typeFooOrBaa = "foo"| "baa"
const catchType = (toCheck: string): void => {
// Working check
if (toCheck === "foo" || toCheck === "baa") {
// No Flow errors
const checkedValue: typeFooOrBaa = toCheck
// ... do something with the checkedValue
}
};
Try it over here
Naturally, I would like to avoid embedding literals.
One of the things I've tried is the equivalent object key test, which doesn't work :-( e.g.
/* #flow */
type typeFooOrBaa = "foo"| "baa"
const fooOrBaaObj = {"foo": 1, "baa": 2}
const catchType = (toCheck: string): void => {
// Non working check
if (fooOrBaaObj[toCheck]) {
/*
The next assignment generates the following Flow error
Cannot assign `toCheck` to `checkedVariable` because: Either string [1] is incompatible
with string literal `foo` [2]. Or string [1] is incompatible with string literal `baa` [3].",
"type"
*/
const checkedVariable: typeFooOrBaa = toCheck
}
};
Try it over here
Is it possible to achieve something like this without having to go down the full flow-runtime route? If so how is it best done?
Thanks for your help.
One approach that appears to works is to use the const object which defines the allowed values, to:
Generate a union type using the $keys utility.
Use that union type to create a map object where the keys are the desired input (our case strings) and the values are "maybe"s of the type that needs refining.
Here's the example from earlier reworked so that it:
Sets the type up as we'd expect to allow either "foo" or "baa" but nothing else.
Detects when a string is suitably refined so that it only contains "foo" or "baa".
Detects when a string might contain something else other than what's expected.
Credit to #vkurchatkin for his answer that helped me crack this (finally).
/* #flow */
// Example of how to persuade Flow to detect safe adequately refined usage of a Union type
// at runtime and its unsafe, inadequately refined counterparts.
const fooOrBaaObj = {foo: 'foo', baa: 'baa'}
type typeFooOrBaa = $Keys<typeof fooOrBaaObj>
// NB: $Keys used inorder for the type definition to avoid aliasing typeFooOrBaa === string
// which allows things like below to correctly spot problems.
//const testFlowSpotsBadDefition: typeFooOrBaa = "make_flow_barf"
const fooOrBaaMap: { [key: string]: ?typeFooOrBaa } = fooOrBaaObj;
// NB: Use of the "?" maybe signifier in the definition a essential to inform Flow that indexing into
// the map "might" produce a "null". Without it the subsequent correct detection of unsafe
// unrefined variables fails.
const catchType = (toCheck: string): void => {
const myValue = fooOrBaaMap[toCheck];
if (myValue) {
// Detects refined safe usage
const checkedVariable: typeFooOrBaa = myValue
}
// Uncommenting the following line correctly causes Flow to flag the unsafe type. Must have the
// "?" in the map defininiton to get Flow to spot this.
//const testFlowSpotsUnrefinedUsage: typeFooOrBaa = myValue
}
Have a play with it over here
You can type the object as {[fooOrBaa]: number}, but flow will not enforce that all members of fooOrBaa exist in the object.

How to avoid using literal strings to narrow disjoint unions in flow

All the examples I find online for narrowing the disjoint union in flowtype uses string literals, like the official one. I would like to know if there is a way to check against a value from an enum like:
const ACTION_A = 'LITERAL_STRING_A';
const ACTION_B = 'LITERAL_STRING_B';
type ActionA = {
// This is not allowed
type: ACTION_A,
// type: 'LITERAL_STRING_A' is allowed
dataA: ActionAData,
}
type ActionB = {
// This is not allowed
type: ACTION_B,
// type: 'LITERAL_STRING_B' is allowed
dataB: ActionBData,
}
type Action = ActionA | ActionB;
function reducer(state: State, action: Action): State {
// Want to narrow Action to ActionA or ActionB based on type
switch (action.type) {
// case 'LITERAL_STRING_A': -- successfully narrow the type
case ACTION_A: // doesn't work
// action.dataA is accessible
...
}
...
}
Unfortunately you can't do these because strings are ineligible as type annotations.
If there is any other way around this that doesn't force typing the string literals everywhere I would love to know.
If there isn't a way around this, also accept suggestions on a higher level how to not need to define these disjoint sets for redux actions.
I'm not in my best shape right now, so sorry if I read your question wrong. I'll try to help anyway. Is this what you're looking for?
const actionTypes = {
FOO: 'FOO',
BAR: 'BAR'
}
type ActionType = $Keys<actionTypes> // one of FOO, BAR
function buzz(actionType: ActionType) {
switch(actionType) {
case actionTypes.FOO:
// blah
}
This should work. Sorry if my syntax is a bit off.
If you're asking how to avoid listing all action types in type Action = ActionA | ActionB then sorry, I don't know, I think this is the way you do it. If I recall correctly, a slightly nicer syntax for defining long unions was recently introduce in Flow:
type Action =
| ActionA
| ActionB
| ActionC
Also, if you don't need individual action types, you can just do
type Action =
| {type: ACTION_A; dataA: ActionAData;}
| {type: ACTION_B; dataB: ActionBData;}
The better way would be to use string literal types for const values:
Try flow...
const ACTION_A:'LITERAL_STRING_A' = 'LITERAL_STRING_A';
const ACTION_B:'LITERAL_STRING_B' = 'LITERAL_STRING_B';

Getting the reference from $firebaseArray to make a new reference object

Related to this question, I need to be able to get an element from a collection as a Firebase reference, ie given a $firebaseArray I need a $firebaseObject pointing to one of its elements. Unlike that example, I can't just hard-code a path the array and take a child from there because the location of the array will vary. And I can't use $firebaseArray.$getRecord() or the object provided by my ng-repeat followed by array.$save() because I may need to do a push() on this element.
So I settled on this reusable approach:
In a service:
function selectElement(array, element) {
var obj = $firebaseObject(array.$ref().child(element.$id));
return obj;
}
In the controller:
function onItemClicked(e) {
vm.selected = dataservice.selectElement(vm.observations, e);
}
In the template:
<div class="list-item" ng-repeat="o in vm.observations" ng-click="vm.onItemClicked(o)">
The first line of selectElement produces an error: array.$ref(...).child is not a function at Object.selectElement
Here array has all the properties you'd expect, but logging array.$ref() shows this obfuscated object:
Y {k: Ji, path: P, n: Ce, pc: true}
That is what it looks like from the time the array is created. What's going on here and how do I use this reference? Is there another way to get a working Firebase object out of an array?

Resources