I am trying to pattern match on an enum when getting an item from my dashmap::DashMap. However, it looks like they have a wrapper type over the Entity when they return the data. How can I pattern match over the items then?
use once_cell::sync::Lazy;
use dashmap::DashMap;
enum Entity {
Person { name: String },
Animal { name: String },
}
static ENTITIES: Lazy<DashMap<usize, Entity>> = Lazy::new(|| DashMap::new());
fn main() {
ENTITIES.insert(
0,
Entity::Animal {
name: "pikachu".into(),
},
);
ENTITIES.insert(
1,
Entity::Person {
name: "trainer mike".into(),
},
);
match ENTITIES.get(&0) {
Some(Entity::Animal { name }) => { // compile error here
println!("found animal: {}", name);
}
_ => panic!("did not find person"),
}
}
And the error:
error[E0308]: mismatched types
--> src\lib.rs:__:__
|
| match ENTITIES.get(&0) {
| -------------- this expression has type `Option<dashmap::mapref::one::Ref<'_, usize, Entity>>`
| Some(Entity::Animal { name }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `dashmap::mapref::one::Ref`, found enum `Entity`
|
= note: expected struct `dashmap::mapref::one::Ref<'_, usize, Entity, >`
found enum `Entity`
Related
I am stuck here:
I have an interface Field which has a name and a value of some unknown type.
interface Field<T> {
name: string;
value: T;
}
I then have a function form, which takes any amount of Field's as rest parameters and returns the data they hold.
function form<T extends Field<unknown>[]>(
...fields: T
): { [k in T[number]['name']]: T[number]['value'] } {
let data = {};
fields.forEach((field) => (data[field.name] = field.value));
return <{ [k in T[number]['name']]: T[number]['value'] }>data;
}
It look like this is action:
const age: Field<number> = { name: 'age', value: 30 };
const sex: Field<string> = { name: 'sex', value: 'men' };
const data = form(age, sex);
// { age: 30, sex: 'men' }
In this example the types of age and sex are just the union of all fields.
data.age; // number | string
data.sex; // number | string
What i want is data to be of type:
const data: { age: number, sex: string } = form(age, sex);
But this returns the error ts(2451).
What does the return type of form need to be.
Is this even possible?
(I'm using typescript version 4.9.3, the latest as of 2022-11-29)
[Edit 2022-11-30]: add Link to working example
typescriptlang.org/play
Your Field should have a ganegic on its name also
interface Field<K extends string, V> {
name: K;
value: V;
}
function ensureField<K extends string, V>(field: Field<K, V>) {
return field;
}
type FieldListToRecord<List extends Field<any, any>[], O extends {} = {}> =
| List extends [infer F extends Field<any, any>, ...infer L extends Field<any, any>[]] ?
FieldListToRecord<L, O & (
F extends Field<infer K, infer V> ? { [k in K]: V }
: never
)>
: { [K in keyof O]: O[K] }; // <- Pure<T>
function form<T extends Field<any, any>[]>(
...fields: T
): FieldListToRecord<T> {
return Object.fromEntries(
fields.map(({ name, value }) => [name, value])
);
}
const age = ensureField({ name: 'age', value: 30 });
// ^?
const sex = ensureField({ name: 'sex', value: 'men' });
// ^?
const data = form(age, sex);
// ^?
// { age: 30, sex: 'men' }
Playground
I'd like to map an array of structs to an array of field values. How would I do this?
pub struct Person {
name: String
}
fn main() {
let my_people = vec![
Person {
name: "Bob".to_string(),
},
Person {
name: "Jill".to_string(),
},
Person {
name: "Rakim".to_string(),
},
];
//map my_people to ["Bob", "Jill", "Rakim"]
}
You have two possible solutions, depending on whether you want to clone the names or borrow them. Both solutions below:
pub struct Person {
name: String,
}
fn people_names_owned(people: &[Person]) -> Vec<String> {
people.iter().map(|p| p.name.clone()).collect()
}
fn people_names_borrowed(people: &[Person]) -> Vec<&str> {
people.iter().map(|p| p.name.as_ref()).collect()
}
fn main() {
let my_people = vec![
Person {
name: "Bob".to_string(),
},
Person {
name: "Jill".to_string(),
},
Person {
name: "Rakim".to_string(),
},
];
println!("{:?}", people_names_owned(&my_people));
println!("{:?}", people_names_borrowed(&my_people));
}
playground
I have a function that accepts a union two disjoint exact types, and constructs a new object. The react documentation indicates that with disjoint exact types, I should be able to infer the type by checking for the existence of a property. However, this doesn't appear to be working -- my else branch indicates that the properties are missing on one of the types.
type Sid = {|
val: string,
id: number
|};
type Entity ={
idOrName: Sid | string,
optionalDescription: ?string
}
const createEntity = (idOrNameAndDescription: Sid | {| name: string, description: string |}): Entity => {
if (idOrNameAndDescription.val) {
return {
idOrName: idOrNameAndDescription,
optionalDescription: null
}
} else {
return {
// These next to lines fail, saying:
// "Flow: Cannot get `idOrNameAndDescription.name` because
// property `name` is missing in `Sid`
idOrName: idOrNameAndDescription.name,
optionalDescription: idOrNameAndDescription.description
}
}
}
See the comment in the else/return statement. The errors given are:
Cannot get idOrNameAndDescription.name because property name is missing in Sid [1].
[1] 60│ const createEntity = (idOrNameAndDescription: Sid | {| name: string, description: string |}): Entity => {
:
65│ }
66│ } else {
67│ return {
68│ idOrName: idOrNameAndDescription.name,
69│ optionalDescription: idOrNameAndDescription.description
70│ }
71│ }
Cannot get idOrNameAndDescription.description because property description is missing in Sid [1].
[1] 60│ const createEntity = (idOrNameAndDescription: Sid | {| name: string, description: string |}): Entity => {
:
66│ } else {
67│ return {
68│ idOrName: idOrNameAndDescription.name,
69│ optionalDescription: idOrNameAndDescription.description
70│ }
71│ }
72│ }
However, as .val was not defined flow should know that it is of the other half of the union and name and description are defined.
I've also tried using if (idOrNameAndDescription.val != null), which makes both of the branches of the if statement invalid as well as if (typeof idOrNameAndDescription.val === 'string'), which results in an even uglier error.
Is this a flow bug or am I missing something?
I have an enum with different variants and I want to find the first variant that matches then transform it by either returning the variant value or mapping it to something else.
In Scala, I'd use case classes to do something like:
data.collectFirst{ case d: DataD => d.data }
In Rust, I have to pattern match twice to achieve the same result. Is there a way to make it less verbose?
enum MyData {
DataA(String),
DataB(u64),
DataC(bool),
DataD { data: String, val: u32 },
}
fn main() {
// test data
let data = vec![
MyData::DataB(42),
MyData::DataD {
data: "meaning of life".to_owned(),
val: 42,
},
MyData::DataC(false),
];
// find first one that matches and map it
let found: Option<String> = data
.iter()
.find(|d| match **d {
MyData::DataD { .. } => true,
_ => false,
})
.and_then(|d| match *d {
MyData::DataD { ref data, .. } => Some(data.to_owned()),
_ => None,
});
}
Since Rust 1.30, you can use Iterator::find_map:
let found: Option<String> = data.iter().find_map(|d| match d {
MyData::DataD { data, .. } => Some(data.to_owned()),
_ => None,
});
Before then, you can use Iterator::filter_map and Iterator::next:
let found: Option<String> = data
.iter()
.filter_map(|d| match d {
MyData::DataD { data, .. } => Some(data.to_owned()),
_ => None,
})
.next();
I don't really like having big match statements in my iterator chains, so I'd normally make a method on MyData to use instead:
enum MyData {
DataA(String),
DataB(u64),
DataC(bool),
DataD { data: String, val: u32 },
}
impl MyData {
fn as_data_d_data(&self) -> Option<&str> {
match self {
MyData::DataD { data, .. } => Some(data),
_ => None,
}
}
}
fn main() {
let data = vec![
MyData::DataB(42),
MyData::DataD {
data: "meaning of life".to_owned(),
val: 42,
},
MyData::DataC(false),
];
let found: Option<String> = data
.iter()
.find_map(MyData::as_data_d_data)
.map(str::to_owned);
}
In fact, a lot of my enums have this kind of pattern:
enum MyData {
DataA(String),
DataB(u64),
DataC(bool),
DataD(D), // made into a struct
}
impl MyData {
fn is_a(&self) -> bool {
match *self {
MyData::DataA(..) => true,
_ => false,
}
}
fn as_a(&self) -> Option<&String> {
match self {
MyData::DataA(x) => Some(x),
_ => None,
}
}
fn into_a(self) -> Option<String> {
match self {
MyData::DataA(x) => Some(x),
_ => None,
}
}
// Repeat for all variants
}
I've even created a crate to automatically derive these functions. I've never published it because I'm pretty sure something similar already exists and I just haven't found it...
enum Foo {
A(..)
B(..)
}
impl Foo {
fn equals(&self, foo: &Foo) -> bool {
let eq = |foo: &Foo| match foo {
A(_) => 0,
B(_) => 1
};
eq(self) == eq(foo)
}
}
I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)