fp-ts: Take an array containing Option<string> elements and transform it into an array of strings, containing only the values of the elements that are defined

Currently, I am in the process of validating a command that is applied to an array representing SVG path data using fp-ts.

type CommandValidation = (commands: CommandArray, nextCommand: Command) => option.Option<string>;

const newCommandValidations: Array<CommandValidation> = [
  validateFirstCommandIsMove,
  validateSymmetricCommandFollowsBezier,
  validateNoMoveAfterMove,
];

export const safelyPushCommand = (command: Command) => either.map((commands: CommandArray) => {
  // The errors are not overlapping yet, but there might be co-existing errors in the future
  const validationErrors = fpFunction.pipe(newCommandValidations,
    fpArray.map((validation) => validation(commands, command)),
    fpArray.filter(option.isSome),
    fpArray.map(option.fold(() => undefined, (some) => some)));

  if (validationErrors.length > 0) {
    return either.left(validationErrors);
  }
  return either.right(pushCommands([command])(commands));
});

Unfortunately, the validationErrors variable is still considered as a list of items with possible "none" values.

How can I obtain an array of strings (specifically typed) that represent any validation errors for this operation?

Should I use an "apply" function to pass parameters into the validation functions?

How can I get an apply function for the validators?

Is there a better way to perform the fold operation when the second function is simply the identity function? EDIT: I had this question addressed in this video https://youtu.be/1LCqHnaJJtY?t=2470 :

option.fold(() => undefined, (some) => some) === option.getOrElse(() => undefined) === option.toUndefined

Many questions from someone navigating through the realm of fp-ts, hoping for some insights to make my project more efficient. The sample code can be found in a branch I created here: https://github.com/justin-hackin/fp-ts-svg-path-d/tree/validators

Answer №1

After reviewing the code you provided, here are some enhancements that I have implemented. I am open to making further adjustments once I receive clarification on certain details.

import * as either from 'fp-ts/lib/Either'
import * as fpFunction from 'fp-ts/lib/function'
import * as option from 'fp-ts/lib/Option'
import * as fpArray from 'fp-ts/lib/Array'

// To ensure code compilation, kept types basic
type CommandArray = unknown[];
type Command = unknown;

type CommandValidation = (commands: CommandArray, nextCommand: Command) => option.Option<string>;

declare const validateFirstCommandIsMove: CommandValidation
declare const validateSymmetricCommandFollowsBezier: CommandValidation
declare const validateNoMoveAfterMove: CommandValidation

const newCommandValidations: Array<CommandValidation> = [
  validateFirstCommandIsMove,
  validateSymmetricCommandFollowsBezier,
  validateNoMoveAfterMove,
];

export const safelyPushCommand = (command: Command) => (commands: CommandArray) => {
  return fpFunction.pipe(
    newCommandValidations,
    fpArray.map((validation) => validation(commands, command)),
    option.sequenceArray,
    option.fold(
      () => either.right(pushCommands([command])(commands)),
      (validationErrors) => either.left(validationErrors)
    )
  );
};

The code above reflects my attempt to make your code work more efficiently. However, it lacks idiomatic clarity.

It is advisable to follow the convention where None signifies failure or absence of value in an Option. Your current use of None for passing validations and Some for failing validations feels counterintuitive.

I recommend using an Either instead, with the Left side reserved for holding validation errors.

A useful technique discussed in this reference -> https://dev.to/gcanti/getting-started-with-fp-ts-either-vs-validation-5eja

Below is a sample solution utilizing either.getApplicativeValidation for running all 3 validation functions and collecting errors in a NonEmptyArray<string>.

import { sequenceT } from 'fp-ts/lib/Apply'
import { getSemigroup, NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'

declare const validateFirstCommandIsMove1: (command: Command) => either.Either<NonEmptyArray<string>, Command>;
declare const validateSymmetricCommandFollowsBezier1: (command: Command) => either.Either<NonEmptyArray<string>, Command>;
declare const validateNoMoveAfterMove1: (command: Command) => either.Either<NonEmptyArray<string>, Command>;

const runValidations = sequenceT(either.getApplicativeValidation(getSemigroup<string>()))

const validateCommand = (command: Command): either.Either<NonEmptyArray<string>, string> => {
  return fpFunction.pipe(
    command,
    runValidations(
      validateFirstCommandIsMove1,
      validateSymmetricCommandFollowsBezier1,
      validateNoMoveAfterMove1
    ),
  )
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The module "node_modules/minimatch/dist/cjs/index" does not contain the exported member 'IOptions'

I am currently working on a TypeScript project that needs to be executed within a Firebase function. Below is the content of my tsconfig.json { "compilerOptions": { "lib": ["es2017"], "module": "commo ...

component is receiving an incompatible argument in its props

I am facing a situation where I have a component that works with a list of items, each with an ID, and a filtering function. The generic type for the items includes an ID property that all items share. Specific types of items may have additional properti ...

Descriptive term that alters a characteristic of a category

I am experimenting with TypeScript (4.5.2) and trying to build a generic that can alter certain properties of a given type. type StrictNumber = { doesNotChange: string, nullable: false, value: number, }; type MakeNullable<T extends { nullable: fa ...

Is there a way to make PrismaClient return DateTime fields as Unix timestamps rather than JavaScript date objects?

When utilizing the PrismaClient for database interaction, DateTime fields are returned as JavaScript Date objects instead of Unix timestamp numbers. Despite being stored as Unix timestamp numbers in the database itself, I require the dates to be retrieved ...

How does f' differ from let f = <F>f?

Here is the code snippet I'm working with: interface F { (): string; a(): number; } function f() { return '3'; } f['a'] = function () { return 3; }; Next, my goal is to assign a function to a variable. This can ...

Unable to establish the relative placement of Div elements

Currently, I am working on an Analog clock project using React, Typescript, and SCSS. My main goal is to keep the CSS code primarily in the SCSS file and minimize its use in inline HTML (or eliminate it completely). Here is an excerpt from my SCSS file: ...

I am having trouble getting the guide for setting up a NextJS app with Typescript to function properly

For some time now, I have been experimenting with integrating Typescript into my NextJS projects. Initially, I believed that getting started with Typescript would be the most challenging part, but it turns out that installing it is proving to be even more ...

Sending input in a nested event listener

I am currently utilizing Highcharts for the purpose of showcasing an interactive map with custom countries. I have a specific requirement to enable the drilldown functionality, which involves clicking on a country to zoom in on another map displaying inter ...

Unraveling Props in a React Component Using Typescript and a Union Interface

In my Props Interface, I have split it into a base interface and two Union types: interface BaseProps { ... } interface ControlledInput extends BaseProps { value: string; onChange: ...; } interface UncontrolledInput extends BaseProps { defaultVa ...

There is no registered handler for channel - Electron IPC handle/invoke

When using my Electron app, I keep encountering the error message "No handler registered for 'channel-name' at EventEmitter../lib/renderer/api/ipc-renderer.ts.ipcRenderer.invoke (electron/js2c/renderer_init.js:1163:19)". This issue seems to stem ...

Encountering a 503 application error code while trying to load the Angular

I am new to deploying my application in Heroku for the first time. Although my deployment was successful, I encountered an error. https://i.sstatic.net/EDB66.png Upon running heroku logs --tail, This is the error message that I am seeing Despite tryin ...

Unexpectedly, the current value is not displayed in the Angular2 dropdown select element that contains multiple group options

Within my Angular2 application, I have a dropdown element with 3 different option groups. Here is how it's structured: <select formControlName="reasonCode" id="reasonCode" class="form-control"> <option value="" [ngValue]="null">< ...

Swap out the selector of an Ionic2 component with its contents

I am utilizing Ionic2 along with TypeScript. Let's assume I desire a custom component to include the content of an ion-menu. <sidemenu></sidemenu> //This sidemenu will contain the ion.menu. <ion-nav id="nav" [root]="rootPage" ...

Can an empty form group be defined and then have form controls added later within the onInit function?

Can someone help me with declaring an empty form group and then adding form controls afterwards? I have tried passing in a null value or not passing anything at all, but it doesn't seem to work. this.demoForm = new FormGroup(null);//NOT WORKING Is th ...

Collection of functions featuring specific data types

I'm currently exploring the idea of composing functions in a way that allows me to specify names, input types, and return types, and then access them from a central function. However, I've encountered an issue where I lose typing information when ...

`The error "mockResolvedValue is not recognized as a function when using partial mocks in Jest with Typescript

Currently, I am attempting to partially mock a module and customize the return value for the mocked method in specific tests. An error is being thrown by Jest: The error message states: "mockedEDSM.getSystemValue.mockResolvedValue is not a function TypeEr ...

Unleash the power of a module by exposing it to the global Window object using the dynamic

In my development process, I am utilizing webpack to bundle and manage my TypeScript modules. However, I am facing a challenge where I need certain modules or chunks to be accessible externally. Can anyone guide me on how to achieve this? Additional conte ...

Is it possible to use export default Enum in TypeScript?

I am facing an issue with exporting an enum object as default at the top level in my code. Here is what I tried: export default enum Hashes{ FOO = 'foo', BAR = 'bar', } However, this resulted in an error message: Module parse failed ...

How do @material-ui/core and @types/material-ui interact with each other?

Exploring a sample project that utilizes material-ui. Upon inspecting the package.json file, I noticed the inclusion of the following packages: { ... "dependencies": { "@material-ui/core": "^1.4.1", ... }, "devDependencies": { "@types ...

What is the best way to simulate Graphql queries and disable the loader in order to test a React component when loading is set to false using Jest and Enzyme?

Currently, I am using code generation on the frontend to generate types and employing GraphQL queries to fetch data in this particular format - const { data, loading, error } = useSampleQuery({ variables: { a: 1, b: 2 } }) When it comes ...