Lets say, that I have this function in Javascript which can generate string based on proper configuration:
function func(config) {
// ...
}
also, let's assume, that the config variable has structure as below (all of these can be not given to function call):
{
"color": string, // can be: "blue", "red", "green"
"number": int, // can be: any number
"other": string, // can be: "x", "y"
}
How to create proper binding for this? I'm stuck with:
[#bs.deriving abstract]
type options = {
[#bs.optional]
color: [#bs.string] [ | `blue | `red | `green ]
[#bs.optional]
number: int,
[#bs.optional]
other: [#bs.string] [ | `x | `y ]
}
[#bs.module]
external func: options => string = "func";
But it does not work when trying to use like this:
let config = MyModule.config(
~color=`blue,
~number=123,
~other=`x
);
let value = MyModule.func(config);
The color and other values are integers, not strings.
This is a case of a JavaScript idiom for named parameters (objects with optional fields), needing to be adapted to the OCaml/ReasonML idiom (functions with actual labelled parameters). You would do this in three steps. Step 1, as Glenn showed, define an external for the config:
type config;
[#bs.obj] external config: (
~color:[#bs.string] [`blue | `red | `green]=?,
~number:int=?,
~other:[#bs.string] [`x | `y]=?,
unit,
) => config = "";
Step 2, bind to the JavaScript function using the JavaScript style of the config object:
[#bs.val] external func: config => string = "";
Step 3, wrap the JavaScript function binding in an OCaml-idiomatic function with labelled parameters:
let func(~color=?, ~number=?, ~other=?, ()) = ()
|> config(~color?, ~number?, ~other?)
|> func;
You can use it like this:
let result = func(~color=`blue, ());
The #bs attributes are often poorly thought out hacks that you shouldn't expect to work well with other attributes, or really with anything other than exactly what the documentation explains or shows examples of. However, if an attribute is used where it is not intended you'll usually at least get a warning about the attribute being unused, which your code does.
#bs.string in particular only works on types at the outermost level of externals, i.e. on types whose values will be passed directly to the external function. There is also a way to create JavaScript objects using external functions which also happens to use less magic and give you much more control over the API. As far as I'm aware, the only downside compared to #bs.deriving is that you can't override field names using something like #bs.as. They have to be valid OCaml identifiers.
Here's your example implemented using an external function annotated with #bs.obj:
type options;
[#bs.obj] external options : (
~color:[#bs.string] [`blue | `red | `green]=?,
~number:int=?,
~other:[#bs.string] [`x | `y]=?,
unit
) => options = "";
To use it you call it exactly as with #bs.deriving:
let config = options(~color=`blue,~number=123, ~other=`x, ());
But even with this I've encountered edge cases where integer values are passed in instead of strings. For this reason I tend to avoid the polymorphic variant attributes altogether and instead use ordinary variants along with conversion functions. This has the added benefit of being more idiomatic, blending in better and being more interoperable with non-BuckleScript code.
Here's what your example might look like using this approach:
type color = Blue | Red | Green;
let colorToString = fun
| Blue => "blue"
| Red => "red"
| Green => "green";
type other = X | Y;
let otherToString = fun
| X => "x"
| Y => "y";
[#bs.obj] external options : (
~color:string=?,
~number:int=?,
~other:string=?,
unit
) => options = "";
[#bs.module] external func: options => string = "func";
let func = (~color=?, ~number=?, ~other=?, ()) =>
func(options(
~color = ?Belt.Option.map(color, colorToString),
~number?,
~other = ?Belt.Option.map(other, otherToString),
()));
let config = func(~color=Blue,~number=123, ~other=X, ());
This is because in reality, those values are variants, instead of trying to make it exactly like JavaScript, I would rather try something more idiomatic to Reason:
type color = Blue | Green | Red;
type coords = X | Y;
type config = {
color,
coords,
number: int
};
let func = (config: config) => "something"
And then inside your function actually return strings (if that is what you really need) by pattern matching on the correct values provided to config.
See the working code here.
Hope it helps!
Related
I want to check if a value exists in an array of objects.
My array looks something like this:
[
0: {
id: 'unique_obj_id',
item: {
id: 'unique_item_id',
...
},
...
},
1: {...}
]
The objects in the array can be one of many interface types (depending on my api call, here: resource strings represent these interfaces, which are defined somewhere else). But one array will always consist of the same interface types for a given data request.
I'm trying to write a reusable function to check whether given_id exists for any object in the array for obj.item.id.
So far I've managed to write the correct logic but typescript throws some exceptions that I can't seem to figure out. shopApprovalData is a collection of interfaces each following the above object structure accessible by the indices of resource.
export type ApprovalResource = 'str1' | 'str2' | 'str3' | 'str4' | 'str5';
export const checkApprovalItem = (given_id: string, resource: ApprovalResource) => {
const shopApprovalData = useShopApprovals();
if (shopApprovalData && shopApprovalData[resource]) {
const resourceApprovalData = shopApprovalData[resource];
if (resourceApprovalData.find(e => e.item.id === id)) {
return true;
}
}
return false;
}
Typescript shows me that shopApprovalData and itemApprovalData is possibly undefined and that the expression find() is not callable since signatures of members of the union type are not compatible with each other. (Apparently, some removes the error, why?)
What approach should I choose instead to check whether the given_id exists in any object of the array?
Based on your usecase, i create this code sandbox: https://codesandbox.io/s/stackoverflow-answer-ke9drk?file=/src/index.ts
explanation:
Type wont compile, so we need something to mark what the interface of the object.
Also i may confused by your code shopApprovalData[resource] what do you want to achieve here? for example, if resource is str1 shopApprovalData[resource] will always return undefined because no index str1 in Array
I hope it help,
Best Regards
So I'm trying to learn writing more generic. I have POST/PUT/DELETE/GET for attatchments together with these entity types (there are also alot more entity types that I will need to add in the future).
This code works, and it is indeed more generic than writing 4 different getAttatchment with hardcorded personnel/workoder etc where it is ${apiType} currently. Some progress made atleast.
But how could I rewrite this even better? I would like to have e.g getAttatchments<T>(EntityType<T> .....) but I don't know how I could write it like that. And ofcourse it need to be typesafe so you don't actually pass entities that does not exist.
export enum EntityType {
Personnel = 'personnel',
Workorder = 'workorder',
Asset = 'asset',
AssetBooking = 'assetbooking',
}
async getAttachments(id: string | number, apiType: EntityType) {
return await this.get<AttachmentDetails[]>(
`attachment/${apiType}/${id}/attachment`
)
}
What you have already looks fairly generic but I would forgo the enum and use a union type instead. e.g.
type EntityType = 'personnel'|'workorder'|'asset'|'assetbooking'
type AttachmentDetails= any // whatever
// mock implementation
function get<T>( url:string) {
return ''
}
function getAttachments<T extends EntityType>(id: string | number, apiType: T) {
return get<AttachmentDetails[]>( `attachment/${apiType}/${id}/attachment` )
}
Playground
I am getting started with fp-ts and was wondering what is the advantage of using Option type over typescript's default notion of optional values represented by question mark?
Typescript tells you whether or not you have the value or not with the question mark. However, often you'll want to perform some sort of computation on them and this is when the Option type from fp-ts shines.
For example, let's say we have two items in a shop and they optionally have prices and we want to write a function to get the price of both of them otherwise we return some value that represents "not for sale". First we'll see how we would do this in vanilla typescript
type ShopItem = {
price?:number
}
const getPrice = (item1: ShopItem, item2: ShopItem):number | null => {
const price1 = item1.price;
const price2 = item2.price;
if (price1 && price2) {
return price1 + price2;
}
return null;
}
This has a few problems. First, there's a bug in there because if the price was 0, then the item should be for sale but instead our falsey check would short circuit out and return null. Secondly, it doesn't scale very well. If we wanted to add multiple ShopItems or even other types of items with optional values, we'll need to keep checking whether or not those values were null.
Now let's compare this to the fp-ts example
https://codesandbox.io/s/laughing-wu-pffe3
import { some, none, ap, Option } from "fp-ts/lib/Option";
import { pipe } from 'fp-ts/lib/pipeable'
type ShopItem = {
price:Option<number> //this type will either be a Some<number> or None
}
const getPrice = (item1: ShopItem, item2: ShopItem): Option<number> =>
pipe(
some((a:number) => (b:number) => a + b),
ap(item1.price),
ap(item2.price)
);
The way this works is that we get the item1 price and the item2 price and we feed it into the addition function inside the some. Notice how there aren't any null checks as this has been abstracted away into our None type.
Consider the following component, which uses a library called styled-components to create a prestyled Text component:
const StyledText = styled(Text)`
font-family: Roboto;
color: ${(props: ITextProps) => props.color || '#000' };
`
where ITextProps is:
interface ITextProps {
color: string;
}
Is there a way to enforce that only valid hex strings are passed to my component?
Ideally the color prop should always match the pattern /#\d{3}(\d{3})?/g, a # followed by at least 3 digits and optionally 3 more digits. If that's not possible, then is there a way to at least enforce that the string is either 4 or 7 characters long?
My research has brought me to a dead end so I'm wondering if anyone on SO might know how I can implement this behavior in TypeScript.
2021-06-13: Updated for TS4.1+
Not truly at compile-time, no. There is a suggestion (now at microsoft/TypeScript#41160) to allow regular-expression validated string types, but it's not clear that it will ever be implemented. If you want to go to that suggestion and give it a 👍 and describe a compelling use case that isn't already listed, it couldn't hurt (but it probably won't really help either).
You could try to use template literal types for this to programmatically generate a big union which matches every acceptable string literal. This even kind of works if you only need three-ish digits:
type UCaseHexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' |
'8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
type HexDigit = UCaseHexDigit | Lowercase<UCaseHexDigit>
type ValidThreeDigitColorString = `#${HexDigit}${HexDigit}${HexDigit}`;
// type ValidThreeDigitColorString = "#000" | "#001" | "#002" | "#003" | "#004" | "#005"
// | "#006" | "#007" | "#008" | "#009" | "#00A" | "#00B" | "#00C" | "#00D" | "#00E"
// | "#00F" | "#00a" | "#00b" | "#00c" | "#00d" | // "#00e"
// | ... 10626 more ... | "#fff"
but since such template literal types can only handle unions on the order of tens-of-thousands of members, this will break if you try to do this with six digits:
type ValidSixDigitColorString =
`#${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}`; // error!
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Expression produces a union type that is too complex to represent
So you'll have to use a workaround.
One workaround is to use template literal types as a generic constraint instead of making ValidColorString a concrete type. Instead, have a type like AsValidColorString<T> that takes a string-type T and checks it to see if it's valid. If it is, it is left alone. If not, then return a valid color string that is "close" to the bad one. For example:
type ToHexDigit<T extends string> = T extends HexDigit ? T : 0;
type AsValidColorString<T extends string> =
T extends `#${infer D1}${infer D2}${infer D3}${infer D4}${infer D5}${infer D6}` ?
`#${ToHexDigit<D1>}${ToHexDigit<D2>}${ToHexDigit<D3>}${ToHexDigit<D4>}${ToHexDigit<D5>}${ToHexDigit<D6>}` :
T extends `#${infer D1}${infer D2}${infer D3}` ?
`#${ToHexDigit<D1>}${ToHexDigit<D2>}${ToHexDigit<D3>}` :
'#000'
const asTextProps = <T extends string>(
textProps: { color: T extends AsValidColorString<T> ? T : AsValidColorString<T> }
) => textProps;
It's pretty complicated; mostly it splits apart the string T and inspects each character, converting bad ones into 0. Then, instead of annotating something as TextProps, you call asTextProps on it for validation:
const textProps = asTextProps({
color: "#abc" // okay
})
const badTextProps = asTextProps({
color: "#00PS1E" // error
// ~~~~~
// Type '"#00PS1E"' is not assignable to type '"#00001E"'.(2322)
})
This works at compile time, but might be more trouble than it's worth.
Finally, you can fall back to pre-TS4.1 solution and make a nominal-ish subtype of string with a user-defined type guard to narrow string values to it... and then jump through all sorts of hoops to use it:
type ValidColorString = string & { __validColorString: true };
function isValidColorString(x: string): x is ValidColorString {
const re = /#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?/g; // you want hex, right?
return re.test(x);
}
Usage:
const textProps: ITextProps = {
color: "#abc"
}; // error, compiler doesn't know that "#abc" is a ValidColorString
const color = "#abc";
if (isValidColorString(color)) {
const textProps2: ITextProps = {
color: color
}; // okay now
} else {
throw new Error("The world has ended");
}
The latter isn't perfect, but it at least gets you a bit closer to enforcing such constraints.
Hope that gives you some ideas; good luck!
Playground link to code
I'm trying to change the value of a global variable transferredData in the function didTransferData(_ data: Data?). My code looks like this:
var transferredData: [Double] = [0.0]
func didTransferData(_ data: Data?) {
var dataArray = [CUnsignedChar](repeating:0, count: (data?.count)!)
data?.copyBytes(to: &dataArray, count: dataArray.count)
let dataInt = dataArray.filter({ Int(String($0)) != nil }).map({ Int(String($0))! })
let dataDouble = dataInt.map { Double($0) }
print("Data double: \(dataDouble)")
transferredData = dataDouble
}
Printing transferredData inside of didTransferData returns the correct array, containing all values of dataDouble. But when I try to access it in this function later on
func returnData() -> [Double]{
print("return Data: \(transferredData)")
return transferredData
}
it returns [0.0]. I'm not sure if I'm missing something crucial here, but I thought that even if I change the value of a global variable in a function, the new value should be accessible for every other functions, too.
Thanks for any advice!!
You said that this code is inside a class. The variable transferredData is not a global, it is a member variable.
There is one of these per object of the class. I suspect that you are not using the same object to make these two calls.
You could make the member shared between all objects by declaring it static, but I think it would be better to leave as is and arrange to use the same object.
EDIT: based on comment
If you write the code
let centralInstance = CentralViewController()
You will get a new object with its own transferredData member initially set to [0.0]. You need to either
Get a reference to the same VC object that has the data
Store the data some where else in an object that you can get back (since the VC might be gone)
To hack something that works (not recommended, but to help understand)
You could move transferredData out of the class and make it an actual global variable. I would do this only to help you get past this issue and understand what's going on.
You could also make it static, which makes all of the VC's share the same instance of the transferredData
When you understand what is going on, you can try to do something a little better (at least move it to its own model object, and not in the VC at all). The next simplest thing is some kind of singleton to manage it -- this is just a glorified global variable but puts you more on the road to a more typical solution.
Where is the problem:
$ swift
Welcome to Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1). Type :help for assistance.
1> var data:[Double] = [0.0]
2.
3. class MuckWithData {
4.
5. func showData () -> [Double] {
6. print ("Data: \(data)")
7. return data
8. }
9.
10. func modifyData () {
11. data = [1.0]
12. }
13. }
data: [Double] = 1 value {
[0] = 0
}
14>
15> var mucked = MuckWithData()
mucked: MuckWithData = {}
16> mucked.showData()
Data: [0.0]
$R0: [Double] = 1 value {
[0] = 0
}
17> mucked.modifyData()
18> mucked.showData()
Data: [1.0]
$R1: [Double] = 1 value {
[0] = 1
}
Probably your computation for dataDouble is actually [0.0] and thus it appears that transferredData didn't change, but it did.