Jestem początkującym funkcjonalnym programowaniem. Pracuję nad natywną aplikacją reagującej za pomocą ramdy. Aplikacja pozwala użytkownikom prowadzić swoje domy.

Mam napisaną funkcję o nazwie asyncPipe, która pozwala mi obiecować i normalne funkcje. Używam go do loginFlow, który obecnie ma żądanie HTTP (getHouseList) jako jego ostatnia funkcja.

const asyncPipe = (...fns) => x => fns.reduce(async (y, f) => f(await y), x);

const loginFlow = asyncPipe(
  // ... someFunctions
  getHouseList
);

// used later like this in LoginForm.js's handleSubmit():
const list = await loginFlow(credentials);

Po zalogowaniu aplikacja ładuje domy użytkownika. Teraz w zależności od tego, czy ma tylko jeden lub wiele domów, które chciałbym wysłać użytkownika, aby wybrać widok, aby wybrać dom lub widok szczegółowy, jeśli ma tylko jeden dom. Dodatkowo chciałbym wysłać akcję Redux, aby zapisać listę w moim reduktorie i kolejnej akcji, aby wybrać dom, jeśli jest tylko jeden.

Obecnie robię to tak:

const list = await loginFlow(credentials);
dispatch(addHouses(list));
if (list.length > 1) {
  navigate('ListScreen')
} else {
  dispatch(pickHouse(list[0]);
  navigate('DetailScreen') ;
}

Ale jak widać, że jest super imperatywny. Wygląda na to, że muszę "rozwidlać" listę i używać go dwa razy w rurze (ponieważ Redux 'dispatch nie ma wartości powrotnej).

Moje główne pytanie brzmi:

Jak zrobić to bardziej funkcjonalne / deklaratywnie (jeśli jest sposób)?

Niewielkie podły, które miałem, czy jest w porządku, aby być tutaj koniecznym / jeśli to działa, jest dobrym pomysłem.

3
J. Hesters 20 luty 2019, 16:02

2 odpowiedzi

Najlepsza odpowiedź

Prawdopodobnie mógłbyś przedłużyć swój rurociąg async, przy użyciu czegoś takiego jak tap:

const loginFlow = asyncPipe(
  // ... some functions
  getHouseList,
  tap(compose(dispatch, addHouses)),
  tap(unless(list => list.length > 1, list => dispatch(pickHouse(list[0])))),
  list => navigate(list.length > 1 ? 'ListScreen' : 'DetailScreen', list)
);

Czy warto to zrobić, zależy od Twojej aplikacji. Jeśli rurociąg jest już długi, to pewnie byłoby czystsze, aby dodać rzeczy do końca w ten sposób, nawet jeśli nie są szczególnie funkcjonalnymi sekcjami. Ale na krótki rurociąg może to nie mieć znaczenia.

Możesz także spojrzeć na już przestarzałe, PipeP lub jego wymiana, pipeWith ({X1}}).

Ale prosiłeś w tytuł o rozwidleniu parametru. {X0}} robi dokładnie, że:

converge(f, [g, h])(x) //=> f(g(x), h(x))

Pozwala to również przekazać więcej niż dwie funkcje i przekazać więcej niż jeden parametr do wynikowej funkcji:

converge(f, [g, h, i])(x, y) //=> f(g(x, y), h(x, y), i(x, y)) 
4
Scott Sauyet 20 luty 2019, 20:37

Biorąc pod uwagę, że możemy użyć R.then i {X1}}, a następnie asyncPipe nie jest naprawdę potrzebna. Jedną z zasadą programowania funkcjonalnego jest faktycznie delegowanie orkiestracji ...

Wreszcie, oczywiście możesz być bardziej deklaratywny, a dobry sposób na rozpoczęcie próbuje uniknąć imperatywnych przepływów kontroli. {x0}} na pewno ci tutaj pomoże :)

Jeśli kod ma efekty uboczne, Następnie użyj R.tap w swoich rurach :)

const fake = cb => () => cb([
  { name: 'Hitmands', id: 1 },
  { name: 'Giuseppe', id: 2 },
]);

const fakeApiCall = () => new Promise(resolve => setTimeout(fake(resolve), 500));
const dispatch = action => data => console.log(`dispatch("${action}")`, data);
const navigate = view => data => console.log(`navigate("${view}")`, data);

const loginFlow = (...fns) => R.pipe(
  R.tap(() => console.log('login Flow Start')),
  fakeApiCall,
  R.then(R.pipe(
    ...fns,
    R.tap(() => console.log('login Flow End')),
  )),
)

const flow = loginFlow(
  R.tap(dispatch('addHouse')), // use tap for side effects
  R.ifElse(
    R.pipe(R.length, R.gt(R.__, 1)), // result.length > 1
    R.tap(navigate('ListScreen')), // onTrue
    R.pipe( // onFalse
      R.tap(dispatch('pickHouse')),
      R.tap(navigate('DetailScreen')),
    ),
  ),
);

/* await */ flow();

/** UPDATES **/
const isXGreaterThan1 = R.gt(R.__, 1);
const isListLengthGreatherThanOne = R.pipe(R.length, isXGreaterThan1);

console.log(`is list.length > 1`, isListLengthGreatherThanOne([1, 2, 3]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([1]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
1
Hitmands 22 luty 2019, 19:07