Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Jan 31 2019 18:12
    ArturAralin closed #312
  • Jan 31 2019 18:12
    ArturAralin commented #312
  • Jan 31 2019 12:57
    codecov[bot] commented #313
  • Jan 31 2019 12:57
    codecov[bot] commented #313
  • Jan 31 2019 12:56
    codecov[bot] commented #313
  • Jan 31 2019 12:56
    codecov[bot] commented #313
  • Jan 31 2019 12:56
    codecov[bot] commented #313
  • Jan 31 2019 12:56
    codecov[bot] commented #313
  • Jan 31 2019 12:55
    codecov[bot] commented #313
  • Jan 31 2019 12:55
    codecov[bot] commented #313
  • Jan 31 2019 12:54
    codecov[bot] commented #313
  • Jan 31 2019 12:54
    codecov[bot] commented #313
  • Jan 31 2019 12:46
    Avaq review_requested #313
  • Jan 31 2019 12:46
    Avaq opened #313
  • Jan 31 2019 12:45

    Avaq on types

    Add misisng type definition for… Add missing type defintion for … (compare)

  • Jan 31 2019 12:38
    Avaq assigned #312
  • Jan 31 2019 12:38
    Avaq commented #312
  • Jan 31 2019 12:16
    kherdier starred fluture-js/Fluture
  • Jan 31 2019 10:45
    ArturAralin commented #312
  • Jan 31 2019 10:35
    Avaq commented #312
jeppe2004
@jeppe2004:matrix.org
[m]
Avaq (Aldwin Vlasblom): wrote a state monad example (https://matrix.to/#/!tntZjsTSRQiWTjlnqS%3Agitter.im/%24CtETLhkKE_iX8gx2rUNKL561tgD_ZoWdqslDlb3GH0Q) but I find it difficult to put into practice.
Diego Ceresuela
@dicearr
I don't think I've ever used Monastic by itself in a project. I would find difficult to give you a realistic example. If you want to use directly monastic by yourself in order to build an HTTP response using middlewares you will most likely end-up writing Momi. So, probably using Momi directly is a good solution. Sorry but I cannot open the example, is asking me to install/use a "client" which I don't have.
Aldwin
@avaq:matrix.org
[m]

Perhaps finding a practical use for the State monad in a language that already supports mutable state in many ways is difficult because the state monad in and of itself doesn't bring anything new to the table.

But it's in the Monad transformation that its power becomes apparent. Being able to enhance any other Monad with mutable state using the Monad transformation interface is why you'd want to capture the feature of mutation in a monadic type.

jeppe2004
@jeppe2004:matrix.org
[m]
dicearr (Diego Ceresuela): it was suppose to be a link to a previous message in this channel but my client, Element, won’t let me link to it in any other way
1 reply
Avaq (Aldwin Vlasblom): I had an issue with a js class that had internal state and I needed to use methods that changed that state. I never found a nice way to have it in my main pipe program. Would that not be a candidate for using a State Monad to wrap it in?
Aldwin
@avaq:matrix.org
[m]
No, jeppe2004 . The State monad adds (simulated) mutable state to an immutable paradigm. It doesn't make something mutable into something immutable. So it's actually the other way around.
What you're describing is probably best wrapped in the IO monad.
jeppe2004
@jeppe2004:matrix.org
[m]
So it’s not a candidate when you need a value to transform and a state-full class instance that is needed to calculate that value? I think I needed to pass an instance around to some functions
Aldwin
@avaq:matrix.org
[m]

No, it's a candidate for when a program has to accumulate state in order to compute its final output, but you don't want to introduce any kind of real mutation.

If you're already starting with a mutable variable or object, you don't need to simulate mutation, because you already have mutation.

Instead, if you start with a mutable class, you might consider creating IO wrappers around the actions that mutate it, so that you force yourself to "handle the mutation with care".

D
@keeoth_gitlab
When Fluture claims to be about twice as fast as Promises, does that include when creating a Future from a Promise?
Aldwin
@avaq:matrix.org
[m]
Hi @keeoth_gitlab, where is this claim made?
Aldwin
@avaq:matrix.org
[m]
Quite a few years ago, Fluture was faster than most Promise implementations. In the meantime, Promises became native and have seen a lot of performance improvements. At the same time, Fluture became a bit slower as a result of trade-offs made for the inclusion of various novel features. Fluture is still quite a fast library, but I don't think it's faster than native Promises.
D
@keeoth_gitlab

@avaq:matrix.org I read it here which does mention native Promises but I suppose it's just outdated information now. Do you have new benchmarks?

https://github.com/fluture-js/Fluture/wiki/Comparison-of-Future-Implementations#-high-performance

Aldwin
@avaq:matrix.org
[m]
@keeoth_gitlab: I ran some small benchmarks (https://github.com/fluture-js/Fluture/tree/master/bench - promise.js) again on Node 16, and now for most of these tests, Fluture appears to be about half as fast.
I've updated the wiki page.
D
@keeoth_gitlab
@avaq:matrix.org Thanks for the updated benchmarks!

I'm sure a number of you may have already heard but there is a new ECMAScript proposal for "Types as Comments". It's basically a way to allow TypeScript and Flow code to run in the browser as is. It's just Stage 0 and might get shelved before ever making it somewhere but the interesting thing is that it's being pushed by representatives from Microsoft and it already has some heavy hitters from the ECMAScript proposal community engaging with it. As we all know, using TS for functional programming has some frustrating limitations and hoops to jump through. So I just want to make sure the functional JS is community aware of the proposal and encourage people to go explore the proposal and speak up so we're represented in the discussion.

Thanks!

https://github.com/giltayar/proposal-types-as-comments

A. Amri
@Amri91
Hi, I am using a library that provides a callback interface which I am wrapping in Future.node. The library unfortunately does not handle a specific exception. What is the best way to handle that exception so my application does not crash? Thank you :)
const F = require ('fluture')
const { retryLinearly } = require ('fluture-retry')

const queryDb = cb ⇒ {
    throw 'ECONNRESET ;('
}

const fQueryDb = F.node (queryDb)

const myFunc => F.go (function* () {
    // fluture-hook and other stuff
    return yield fQueryDb
})

const myOtherFunc => F.go (function* () {
    // Other stuff
    yield retryLinearly (myFunc)
})

F.fork (console.error) (console.log) (myOtherFunc)
A. Amri
@Amri91
This works for me :D
It seems so obvious now, I guess I needed a break.
const fQueryDb = F.node (cb => {
    try {
        queryDb (cb)
    } catch (e) {
        cb (e)
    }
})
Diego Ceresuela
@dicearr
Hey! :wave: Yeah that would be my way to go if the queryDb function may throw synchronously.
Jon Ege Ronnenberg
@dotnetCarpenter

Does anyone know the Fluture equivalent of tagBy?
Something akind to:

(a -> Boolean) -> Future e a -> Future e a

where the returned Future is either resolved a or rejected a?
I want to check the Response.ok property and either reject a or resolve a.

Jon Ege Ronnenberg
@dotnetCarpenter
Edit the signature:
(a -> Boolean) -> a -> Future a a

I came up with this:

//    tagByF :: (a -> Boolean) -> a -> Future a a
const tagByF = f => x => f (x)
  ? F.resolve (x)
  : F.reject  (x)

//    request :: String -> Future e a
const request = S.pipe ([
  F.encaseP (fetch),
  S.chain (tagByF (S.prop ('ok'))),
  F.coalesce (response => F.reject (`HTTP error! status: ${response.status}`))
             (F.encaseP (response => response.text ())),
  S.join,
])

F.fork (console.error.bind (console, 'Error:'))
       (console.log.bind (console, 'Success:'))
       (request ('https://www.example.com/'))

Seems to work fine but I'm open to improvements ;)

Jon Ege Ronnenberg
@dotnetCarpenter
:point_up: You'll need to tell sanctuary about the Response object though, like so:
const toString = Object.prototype.toString
const $Response = $.NullaryType
  ('Response')
  ('https://devdocs.io/dom/response')
  ([])
  (x => toString.call (x) === '[object Response]')
Jon Ege Ronnenberg
@dotnetCarpenter

After contemplating the cancel functionality of a Future and how it didn't really work with my previous solution, I came up with:

import { S, F } from './sanctuary.js'

//    tagByF :: (a -> Boolean) -> a -> Future e a
const tagByF = f => x => f (x)
  ? F.resolve (x)
  : F.reject  (x)

//    request :: String -> Future e a
const request = url => F.Future ((reject, resolve) => {
  const controller = new AbortController ()

  fetch (url, { signal: controller.signal, redirect: 'follow' })
    .then (resolve)
    .catch (reject)

  return () => {
    controller.abort ()
  }
})

//    responseHandler:: String -> Future e a
const responseHandler = S.pipe ([
  S.chain (tagByF (S.prop ('ok'))),
  F.coalesce (response => F.reject (`HTTP error! status: ${response.status}`))
             (F.encaseP (response => response.text ())),
  S.join,
])

export default S.compose (responseHandler)
                         (request)

And here is a simple use-case:

import request from './request.js'

const baseUrl = language => `https://raw.githubusercontent.com/OpenXcom/OpenXcom/master/bin/common/SoldierName/${language}.nam`
const swedishUrl = baseUrl ('Swedish')

const cancel = F.fork (console.error)
                      (console.log)
                      (request (swedishUrl))
cancel ()
dotnetCarpenter @dotnetCarpenter not sure what is happening with the colorization of js code on gitter.im - seems it doesn't like `
Jon Ege Ronnenberg
@dotnetCarpenter
Even better with caller define option object:
//    request :: StrMap -> Future e a
const request = ({url, ...options}) => F.Future ((reject, resolve) => {
  const controller = new AbortController ()

  fetch (url, { signal: controller.signal, ...options })
    .then (resolve)
    .catch (reject)

  return () => {
    controller.abort ()
  }
})

...

const cancel = F.fork (console.error)
                      (console.log)
                      (request ({url:swedishUrl, redirect: 'follow'}))
or I could use a Request object...
Jon Ege Ronnenberg
@dotnetCarpenter

I'm beginning to see some flaws in my fetch ecapsulation and hoping for input from you on how to fix them.
First I'll post my current code and then discuss my issue with handling rejections.

import { S, F } from './sanctuary.js'

//    tagByF :: (a -> Boolean) -> a -> Future a a
const tagByF = f => x => f (x)
  ? F.resolve (x)
  : F.reject  (x)

//    request :: StrMap -> Future e a
const request = ({url, ...options}) => F.Future ((reject, resolve) => {
  const controller = new AbortController ()

  fetch (url, { signal: controller.signal, ...options })
    .then (resolve)
    .catch (reject)

  return () => {
    controller.abort ()
  }
})

//    responseToText :: Future e Response -> Future String String
const responseToText = S.pipe ([
  S.chain (tagByF (S.prop ('ok'))),
  F.coalesce (response => F.reject (`HTTP error! status: ${response.status}`))
             (F.encaseP (response => response.text ())),
  S.join,
])

//    responseToHeaders :: Future e Response -> Future String String
const responseToHeaders = S.pipe ([
  S.chain (tagByF (S.prop ('ok'))),
  F.coalesce (response => F.reject (`HTTP error! status: ${response.status}`))
             (S.compose (F.resolve)
                        (S.prop ('headers'))),
  S.join,
])

I got two different Response handlers, that I can use to either get the text from a Response or the headers.

The issue with them is that they can only handle rejected Response. E.i. Future Response Response. If there is a network error, then signature is Future Error a. S.chain is shorthand for map -> of -> join so that code path is not called on rejection, F.coalesce is called and will fail if its argument is Future Error Response. I can not wrap it in S.chain because then F.coalesce will not get a Response when the status code is not in the 200–299 range. E.g. Future Response Response.

I could handle it inside F.coalesce but I was thinking that there might be Fluture idiomatic way to handle this kind of situation.

//    rejectionToString :: Response|Error|Any -> Future String a
const rejectionToString = e => {
  let rejection

  switch (e.constructor) {
    case Response:
      rejection = `HTTP error! status: ${e.status}`
      break
    case Error:
    case TypeError:
      rejection = `HTTP error! message: ${e.message}`
      break
    default:
      rejection = `Unknown rejection: ${String (e)}`
  }

  return F.reject (rejection)
}

//    responseToText :: Future e Response -> Future String String
const responseToText = S.pipe ([
  S.chain (tagByF (S.prop ('ok'))),
  F.coalesce (rejectionToString)
             (F.encaseP (response => response.text ())),
  S.join,
])

//    responseToHeaders :: Future e Response -> Future String String
const responseToHeaders = S.pipe ([
  S.chain (tagByF (S.prop ('ok'))),
  F.coalesce (rejectionToString)
             (S.compose (F.resolve)
                        (S.prop ('headers'))),
  S.join,
])

There is a live example at https://dotnetcarpenter.github.io/common-scandi-names/. Because I use the range header, it unfortunately does not work in Firefox because of Bug 1733981 since it is a new feature added just 6 months ago: whatwg/fetch#1312.
It looks like it doesn't work in Safari either.

Jon Ege Ronnenberg
@dotnetCarpenter
Safari got a patch today that will mark the range header safe, meaning no preflight request! https://bugs.webkit.org/show_bug.cgi?id=231174
Jon Ege Ronnenberg
@dotnetCarpenter

I feel I do something wrong when I end up with return signatures where a value type is sometimes fixed:

tagByOk :: Future e Response -> Future (e|Response) Response

as in:

//    tagByF :: (a -> Boolean) -> a -> Future a a
const tagByF = f => x => f (x)
  ? F.resolve (x)
  : F.reject  (x)

//    tagByOk :: Future e Response -> Future (e|Response) Response
const tagByOk = S.chain (tagByF (S.prop ('ok')))

If the argument to tagByOk is a resolved Response then the return value is resolve (Response) or reject (Response), e.g. Future Response Response. But if the argument is a rejected future, then the return value is that rejected future and can hold whatever value. E.g. e.

Am I setting myself up for trouble later on and how should change my functions to not have multiple return types?

It is useful though, to have small functions that only do one thing, so they can easily be reused...

//    responseToText :: Future e Response -> Future String String
const responseToText = S.pipe ([
  tagByOk,
  rejectToStringOrResolveTo (
    F.encaseP (response => response.text ())
  ),
  S.join,
])

//    responseToHeaders :: Future e Response -> Future String String
const responseToHeaders = S.pipe ([
  tagByOk,
  rejectToStringOrResolveTo (
    S.compose (F.resolve)
              (S.prop ('headers'))
  ),
  S.join,
  S.map (Array.from),
  S.map (S.reduce (s => t => s + `${t[0]}: ${t[1]}\n`) (''))
])
rejectToStringOrResolveTo :: Future String a
Aldwin
@avaq:matrix.org
[m]
@dotnetCarpenter: I recommend not chaining the tagged response. Probably better to use S.tagBy instead of the tagByF function and have a Future Error (Either Response Response)) structure.
Jon Ege Ronnenberg
@dotnetCarpenter

@avaq:matrix.org thanks. I think I got it with this mockup:

const failureResponse = url => ({
  ok: false,
  status: 500,
  text () {
    throw 'No no no'
  }
})
const successResponse = url => ({
  ok: true,
  status: 200,
  text () {
    return Promise.resolve (`The body from ${url}`)
  }
})

const fetchException = url => { throw new Error (url) }
const fetchFail      = url => Promise.resolve (failureResponse (url))
const fetchSuccess   = url => Promise.resolve (successResponse (url))

//    request :: Future Response Response -> Future String String
const responseHandler = S.pipe ([
  S.map (S.tagBy (S.prop ('ok'))),

  F.map (S.either (response => F.reject (`HTTP error! status: ${response.status}`))
                  (F.encaseP (response => (response.text ())))),

  S.join,
])

const executeAndLog = (
  F.forkCatch (e => console.error (`Fatal Error: ${e.message}\n${e.stack}`))
              (console.error.bind (console, 'Error:'))
              (console.log.bind (console, 'Success:'))
)

executeAndLog (responseHandler (F.encaseP (fetchFail) ('this is my URL')))
executeAndLog (responseHandler (F.encaseP (fetchSuccess) ('this is my URL')))
executeAndLog (responseHandler (F.encaseP (fetchException) ('this is my URL')))

Outputs:

Fatal Error: this is my URL
Error: this is my URL
    at fetchException (file:///home/dotnet/projects/playground/common-scandi-names/ternary-future.js:25:39)
    at Interpreter.EncaseP$interpret [as _interpret] (file:///home/dotnet/projects/playground/common-scandi-names/node_modules/fluture/src/encase-p.js:17:9)
    ...
    at ModuleJob.run (node:internal/modules/esm/module_job:197:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
Error: HTTP error! status: 500
Success: The body from this is my URL
A. Amri
@Amri91

Is this the right way to add a timeout to a Future? My future is not timing out. It just hangs.

// Timeout after 20 minutes
yield F.race (F.rejectAfter (1000 60 20) (‘Task timed out’)) (someFuture)

Jon Ege Ronnenberg
@dotnetCarpenter
That look right. But it will yield a Future that needs to be forked - not a result. E.i:
const futureResult = F.race (F.rejectAfter (20) ('Task timed out')) (F.after (22) ('Task resolved'))
F.fork (console.error)
       (console.log)
       (futureResult) // -> Task timed out
Jon Ege Ronnenberg
@dotnetCarpenter

Hmm but there seem to be a case with generators that doesn't quite work. E.g.

Works:

import Do from 'lazy-do'

const futureResult = Do (function* () {
  yield F.race (F.rejectAfter (20) ('Task timed out')) (F.after (22) ('Task resolved'))
}, F.Future)

F.fork (console.error)
       (console.log)
       (futureResult) // -> Task timed out

Does not work:

import Do from 'lazy-do'

const futureResult = Do (function* () {
  yield F.race (F.rejectAfter (20) ('Task timed out')) (F.after (10) ('Task resolved'))
}, F.Future)

F.fork (console.error)
       (console.log)
       (futureResult) // -> Error: [lazy-do] returned value is not a Monad: undefined.

Call stack:

Error: [lazy-do] returned value is not a Monad: undefined.
    at step (node_modules/lazy-do/index.js:16:15)
    at call (file://node_modules/fluture/src/internal/utils.js:8:36)
    at Transformation.ChainTransformation$resolved (file://node_modules/fluture/src/future.js:497:62)
    at Transformation.transformationHandler [as resolved] (file://node_modules/fluture/src/future.js:427:19)
    at resolved (file://node_modules/fluture/src/future.js:306:27)
    at Interpreter.Resolve$interpret [as _interpret] (file://node_modules/fluture/src/future.js:225:3)
    at drain (file://node_modules/fluture/src/future.js:369:25)
    at settle (file://node_modules/fluture/src/future.js:292:15)
    at Timeout.resolved [as _onTimeout] (file://node_modules/fluture/src/future.js:306:5)
    at listOnTimeout (node:internal/timers:561:11)
Jon Ege Ronnenberg
@dotnetCarpenter
ehh.. my mistake. My futureResult never ends (e.i. never returns { done:true, value: Future }).
This works:
import { S, F } from './sanctuary.js'
import Do       from 'lazy-do'

const futureResult = Do (function* () {
  return F.race (F.rejectAfter (20) ('Task timed out')) (F.after (10) ('Task resolved'))
}, F.Future)

F.fork (console.error)
       (console.log)
       (futureResult) // -> Task resolved
Jon Ege Ronnenberg
@dotnetCarpenter
Can someone explain to me what "can be any lazily evaluated monadic structure" means, in lazy-do? All monads are lazily evaluated, right? If I read lazily evaluated as in the example, it looks like Do only supports monads asynchronous functions, but it works fine with sanctuary-identity:
import { S }    from './sanctuary.js'
import Do       from 'lazy-do'
import Identity from 'sanctuary-identity'

const test = Do (function* () {
  let word1 = yield S.of (Identity) ('hello')
  let word2 = yield S.of (Identity) (`${word1} `)
  let word3 = yield S.of (Identity) (`${word2}world`)

  return S.of (Identity) (`${word3}!`)
}, Identity)

const result = S.extract (test)
console.log (test) // -> Identity ("hello world!")
console.log (result) // -> hello world!
Would it not be fair to say that lazy-do supports "any monadic structure"?
A. Amri
@Amri91
Thanks @dotnetCarpenter :)
Nedal
@cipherlogs
// Hello guys can someone explain to me how to get around this error

// here I'm trying to consume the `future` response when the predicate is true
// but I'm getting this error
// Uncaught TypeError: Type-variable constraint violation
// when :: (a -> Boolean) -> (a -> a) -> a -> a

S.when(S.K(is_request_ready))
      (fork(log) (log))
      (S.fromMaybe(reject("Can't call api!")) (response));

// I added FlutureEnv to my Sanctuary module, but I don't know why Sanctuary isn't satisfied yet
// for now I'm using S.unchecked.when

// btw, even that I get the error, the Future gets consumed successfully
4 replies
Nedal
@cipherlogs

Say for example I'm making the following future

        encaseP(fetch) (api)
        .pipe(chain(encaseP(res => res.json())))
        .pipe(map...)
...
        .pipe(map(S.ifElse(I want to reject this) (reject("Error")) (resolve("success"))
        .pipe(map(S.prop("test")) // Why this thing will get evualuated if the previous statement is returning a rejection
        .fork(log) (log)

isn't one the futures of Futures is, if the thing gets rejected it will not run map, it will keep on skipping until it reaches the end then it logs the error
but in this case it is not working !!

Jon Ege Ronnenberg
@dotnetCarpenter
It looks like you got a resolved Future which encapsulate a rejected Future. Since the outer Future is resolved, map will run the supplied function.
Jon Ege Ronnenberg
@dotnetCarpenter
.pipe(map(S.ifElse(I want to reject this) (reject("Error")) (resolve("success"))
will produce Future a (Future String String)
Jon Ege Ronnenberg
@dotnetCarpenter
of course you'll need to fix S.ifElse(I want to reject this) to be valid JS
Nedal
@cipherlogs

@dotnetCarpenter Oh, you mean if I have used chain instead of map ... all the following maps will be skipped

.pipe(map(S.ifElse(I want to reject this) (reject("Error")) (resolve("success"))

will produce Future a (Future String String)

Jon Ege Ronnenberg
@dotnetCarpenter
Probably. I can only see a very limited portion of your program, so there is no way I can be sure. But yes, chain can be thought of as S.map and then S.join, which will collapse monadic contexts. As described much better in Mostly Adequate Guide, Chapter 09: Monadic Onions - My Chain Hits My Chest.
Nedal
@cipherlogs
@dotnetCarpenter, Thank you. Yes I was tired and I didn't pay attention. I have added now S.show so that it is obvious for me when debugging " Future(Future) "
Jon Ege Ronnenberg
@dotnetCarpenter
:+1:
Rizky Muhammad Baihaqy
@rizkybaihaqy
Hi, I am new to this library. I was trying to fork the future through Sanctuary pipe. its working, but the console log output is nested. Is it okay to have nested future? The snippet below is the console log output.
Future {
  context: <ref *1> List { head: null, tail: [Circular *1] },
  '$1': Future {
    context: <ref *1> List { head: null, tail: [Circular *1] },
    '$1': {
      message_id: 784,
      date: 1660667609,
    },
    '$2': undefined,
    '$3': undefined
  },
  '$2': List {
    head: { context: [List], '$1': [Function (anonymous)], '$2': undefined },
    tail: <ref *1> List { head: null, tail: [Circular *1] }
  },
  '$3': undefined
}