Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
Nedal
@cipherlogs

@davidchambers Oh now I see, I thought you are using Object.create with daggy that's why I was confused. You are not using daggy at all.

what do you think if there was a daggy like library that integrates so well with fantasy land and sanctuary,
won't allow the use of this, be more composable, and offers creating types with less boilerplate code

would that be veery helpful ?

David Chambers
@davidchambers
That would be helpful, yes. :D
Nedal
@cipherlogs
@davidchambers Thank you, I will give it a shot :)
Nedal
@cipherlogs

@davidchambers Hello David, I'm not sure if I'm doing something wrong or sanctuary-def is not behaving properly.
I'm trying to create a predicate that validates if a function accepts its first argument only as an array

const input_type = $.Array ($.Unknown);
const output_type = $.Unknown;
const does_only_take_array = S.is ($.Fn (input_type) (output_type));

console.log (does_only_take_array (x => "x")); // true
console.log (does_only_take_array (xs => xs[0] + xs[1])); // true

both examples print true, that's not correct
so what qualifies that the function accepts an array as an argument ??

Also I'm wondering, don't you think that Sanctuary could benifit from having something like S.curry_n.
it would be nice to curry external functions and make them accept args one at a time only.
David Chambers
@davidchambers

@cipherlogs, there's no generalizable way of determining the type of a JavaScript function.

S.is ($.Fn (i) (o)) (x) is more or less equivalent to typeof x === 'function'.

We removed Sanctuary's curry* functions in #692. The pull request's description explains the motivation.
Nedal
@cipherlogs

@davidchambers Thank you, David.
I do agree with what has been said in that pull request. it makes total sense. and I'm liking to see that Sanctuary is pushing toward keeping what's essential only.

about the sanctuary-def issue, it will be nice if there was a way to figure out a reliable solution.

I thought maybe we can try to encase the function and pass input_type then if it doesn't blow up (returns Right) that means the function was able to handle
the input_type otherwise it wasn't.

and if the user specifies the output type (rather than just $.Unknown) it would be easy to test if the result matches the givenoutput_type
thus more reliable

const takes_array_only = f =>
    S.either (S.K (false))
             (S.K (true))
             (U.encase (f) ([1, 2, ""]));

console.log (takes_array_only (x => "x")); // true
console.log (takes_array_only (str => str.toUpper())); // false
console.log (takes_array_only (xs => xs[0] + xs[1])); // true
David Chambers
@davidchambers
@cipherlogs, the problem I see is that if you want to verify that a function returns a Bar when applied to a Foo, you're going to need to get a Foo from somewhere.
sanctuary-def knows how to test whether a value is a Foo, but has no idea how to make a Foo.
Nedal
@cipherlogs

@davidchambers Yes, some foos are easy to make some are maybe hard

  • $.Array ($.Unknown) could be represented as [1, 2, ""], [x => x, {}, ""], [1, 1, 1] ...
  • $.Array ($.String) could be represented as ["a", "bb", "c"]

foo can be made, but I might be forgetting some weird edges where the validation would fail.

David Chambers
@davidchambers
What about if Foo is a user-defined type?
Nedal
@cipherlogs

@davidchambers If we have a String, we have access to Empty of String so we are able to create something of type String.
but not all types would have Empty.
for the primitive types that come with Javascript we can easily create a list of values that will represent each type and use it.

if the type is defined by the user, why not ask the user while creating the type with sanctuary-type-identifiers to pass an example of the type
in case the type doesn't have empty

I don't know how practical this is, but sanctuary-def is really good, making it more capable that would open a lot of doors.

Walker
@walkermalling

Hi folks, I have a type constraint question.

I'm trying to parse args from a POST body, and my approach is to define the required arguments in terms of resolver functions that parse the values from the request object, returning Left of an error message or Right of the value.

However, when I filter for errors, it throws because the values of the args are of different types.

Contrived example:

let parsedArgs = [
  { name: 'id', value: S.Right(4)},
  { name: 'title', value: S.Right('some title')},
  { name: 'byline', value: S.Left("byline is required")}
 ];

let errors = S.pipe([
  S.map (S.prop ('value')),
  S.lefts,
  S.joinWith (', ')
]) (parsedArgs)

The first function in the pipe is where the error is:

Uncaught TypeError: Type-variable constraint violation

map :: Functor f => (a -> b) -> f a -> f b
                          ^
                          1

1)  Right (4) :: Either c Number
      Right ("title") :: Either c String

What would be a good way around this? I'm open to any suggestions, but hoping for something that confronts the reality of the heterogeneity of the values rather than just avoiding the constraint.

Nedal
@cipherlogs

@walkermalling

Yes, the fastest way to solve your problem is by using the unchecked version of Sanctuary.

const U = S.Unchecked;

then you have to replace S.pipe with U.pipe same thing with U.map, U.lefts, U.joinWith.

However, I believe that you want to solve the problem without relying too much on U.
we can use U one time to fix the output and make it compatible, then it would be easy to compose it with other Sanctuary functions.
better than using a bunch of U all over the place.

looking at your example you are trying to concat all error messages to a String, what if we create a utility that will always guarantee the return type to be of type String,
assuming that the Object is safe, and nothing is missing.

// :: Object -> String
const get_value = S.compose (from_left) (S.prop ("value"));

that's it, now I can reduce the whole heterogeneity Object with concat and get_value

S.reduce (acc => x => S.concat (acc) (get_value (x)) ("") (parsedArgs)

// :: Either a b -> String
const from_left = S.either (x => `${x}`) (S.K (""));

// :: Object -> String
const get_value = S.compose (from_left) (S.prop ("value"));

// (a -> String) -> String -> a -> String
const concat_with = S.lift2 (S.concat) (S.I);

// Object -> String
const get_errors = U.reduce (concat_with (get_value)) ("");

console.log (get_errors (parsedArgs);
Walker
@walkermalling

Thanks @cipherlogs , I played around with your suggestion and it works well, the essential part being the S.either (x =>${x}) (S.K ('')) to guarantee every member of the structure is a string.

Stepping back, however, there seems like there should be a way to do it without basically coercing everything to the same type. What I mean is that the Either type is supposed to protect you from applying a function to a value that it cannot be applied to. It seems like creating a structure of Eithers should be mappable, trusting the container type to prevent a mismatch between the functor and the contained value. -- I understand that I have Rights with different types, but I'm also explicitly not touching their values. Given the type checking, I can't even filter out the Rights, because the functor used to filter will throw. Thoughts?

Nedal
@cipherlogs

@walkermalling
Yes, of course you can map and filter them S.filter (S.compose (S.isLeft) (S.prop ("value"))
but then what? there isn't much useful we can do with them wrapped, we have to unwrap them ...
and to unwrap them you have to define a fallback value, to unwrap Left ("hi") the fallback value must be of type String.
so to unwrap a collection of different types, there's no way that we can come up with a fallback/default value that will work for all of them unless we break the type constraint.

if we omit the fallback value, then we are back to square one where if we are lucky we will get our data if we are not it will blow up with an error
= unsafe/unpredictable

I guess Sanctuary is being helpful here, alerting us ahead of time that we have an array of types that are not the same members.

[Left ("hello"), Left(x => x), Left (4), Left (undefined)] sooner you will unwrap these values.

Walker
@walkermalling

Hm, I just tried it again and the filter worked, I must have implemented it incorrectly when I tried that before--perhaps note placing it early enough in the pipe.

I understand what you are saying about anticipating the error when you unwrap the values, and I sympathize with that. I'm still wondering what the most type-expressive way of handling heterogeneous values would be. I suppose, in this case, that might lead to defining a polymorphic data type for the query args?

Nedal
@cipherlogs

@walkermalling, I'm glad it worked.
What do you mean by most type-expressive do you have any examples?

About heterogeneous values, it depends but I honestly don't find myself in situations where it bothers me at all.
I think you can do most at parsing/validation, then your parsed data will be orgnized the way you want.

sometimes I find myself needing two different types at once S.pair is helpful. if I need more I just re think of solving the problem in a better way.

redcedar
@redcedar:matrix.org
[m]
By expressive I mean, for example, that the container type itself provides information about the data. Continuing the case above: it should be correct to have a heterogeneous list because, (1) the list expresses the correct relationship between args, (2) the args are of different types according to the domain. And when it is parsed, it should be correct to have a list of Eithers of different types.
Nedal
@cipherlogs

@redcedar:matrix.org that's an interesting idea

however I'm still new to this field and it is hard to say if the idea is worth it or not.
That's why I'm interested in learning Haskell and Clojure.

But I heard that many people go through that learning phase every time they download a Haskell package, you have new types that you need to learn about in order to use that package!

Maybe in Haskell it is worth it, but in a dynamic language like JS is it ?

3 replies
Nedal
@cipherlogs

@davidchambers David is there a way we can safely do reccursion with Sanctuary. I'm tryin to deep freeze an object S.unfoldr won't work for that.

does sanctuary have a trampoline ?

David Chambers
@davidchambers
There’s no trampoline function. Perhaps we should add one. Do you have a type in mind?
Nedal
@cipherlogs

@davidchambers Does it need to have a type?
I thought maybe just a utility that takes an unsafe function and makes it stack safe.

Also, I found that using Sanctuary-def with recursion makes the calculation slow. I wonder if the type checking could be bypassed somehow just while the recursion is running.

David Chambers
@davidchambers
@cipherlogs, I meant the type of the utility function you have in mind.
Nedal
@cipherlogs

@davidchambers what do you think about the following
trampoline :: (a -> (() -> b)) -> (a -> b)

or can we implement them and wrap them with Maybe ?

David Chambers
@davidchambers

@cipherlogs, I think this is the only way to get an a -> b from an a -> () -> b:

//    asdf :: (a -> () -> b) -> a -> b
const asdf = f => a => f (a) ();

Trampolining requires something more sophisticated, I think.

Nedal
@cipherlogs
@davidchambers
// yes it does require a bit more, as an example we can't keep on
// invoking the `f` until it becomes `b`
// what if `b` is a function too
// that's why the other time I asked you if you wanna wrap it
// inside a `Just`

// :: Function -> Boolean
const get = S.get (S.K (true));
const name_of = S.compose (S.fromMaybe ("")) (get ("name"));

// :: Function -> Boolean
const is_lazy = S.compose (S.equals ("lazy")) (name_of);

// :: (a -> Just (b)) -> a -> b
const pop = f => a => {
    f = f (a);

    while (is_lazy (f)) {
        f = f();
    }

    return f;
}

// from (3) // prints 3 2 1 0
// from (1) // prints 1 0
const from = end => {
    return (
        end !== 0
        ? [end, ...from (end - 1)]
        : [end]
    );
}

const safe_from = end => {
    return (
        end !== 0
        ? function lazy() { return [end, ...from (end - 1)]; }
        : [end]
    );
}

console.log (
    from (3),
    pop (safe_from) (3)
);
Nedal
@cipherlogs

@davidchambers basically, pop() is just popping the function in case its name is "lazy". otherwise it won't.
so the user must know in advance that they need to wrap in a function with a name "lazy"

we can acheive the same thing by telling the user to wrap in Either or Maybe

is there something else that trampoline needs to do ?

David Chambers
@davidchambers
@cipherlogs, pop (safe_from) (10_000) blows the stack on my computer, I think because safe_from calls from. Can you share a working version?
Nedal
@cipherlogs

@davidchambers My apologies I forgot to test. I felt the need to explain in code instead of explaining it in English and I forgot.

Yes, instead of recursively calling safe_from I called from.
after I changed it I reliezed that I need to wrap what's inside and create an ad hoc so that I can spread the result of the lazy function.

instead I just wrote the function again with two args
the first argument is for accumulation.

const range = acc => x => {
    return (
        x !== 0
        ? function lazy () { return range ([...acc, x]) (x-1) }
        : [...acc, x]
    );
};

const safe_from = range ([]);
David Chambers
@davidchambers
That's interesting, @cipherlogs. Does that suggest to you that laziness should be “baked in” rather than provided by a trampoline function?
Nedal
@cipherlogs

@davidchambers It would be awesome if trampoline would take care of that but I don't see how!

After giving it a second thought, I think there's another importan option which is writing the function with tail call optimization, then a utility from Sanctuary would compile that to a CPS function.
Once tail call is fully supported, Sanctuary can easily just turn that utility function as Apply combinator and nothing would break.

for the functions that cannot be written with tail call, we can use trampoline.

David Chambers
@davidchambers
@silly-goat
S.size (S.unfoldr (n => n === 0 ? S.Nothing : S.Just (S.Pair (n) (n - 1))) (10_000))
silly-goat
@silly-goat
Script execution timed out after 5000ms
David Chambers
@davidchambers
@silly-goat
S.size (S.unchecked.unfoldr (n => n === 0 ? S.Nothing : S.Just (S.Pair (n) (n - 1))) (10_000))
silly-goat
@silly-goat
Script execution timed out after 5000ms
David Chambers
@davidchambers
@silly-goat
S.unchecked.size (S.unchecked.unfoldr (n => n === 0 ? S.unchecked.Nothing : S.unchecked.Just (S.unchecked.Pair (n) (n - 1))) (10_000))
silly-goat
@silly-goat
10000
David Chambers
@davidchambers
:point_up: @cipherlogs, have a look at S.unfoldr.
Nedal
@cipherlogs

@davidchambers That's interesting, can you please explain how come disabling checkTypes helps ?

I thought checkTypes is only for disabling/enabling type checking.
does it have a stack limit or a time out limit ?

is it a good practice to write recursive functions with a mindset that CheckTypes will be disabled later?

Nedal
@cipherlogs

@davidchambers

Also It is not possible to write all recursive implementations with unfoldr.

  1. let's say you want to plug future args inside the calculation (not possible with unfoldr once the calculation starts, you can't inject anything to it), also the output is always an array, and it is accumulative, so you have to reduce it and do extra work. it feels like cpu waste.
David Chambers
@davidchambers
The Gitter bot has a time limit. That's nothing specific to Sanctuary.
Nedal
@cipherlogs

@davidchambers I see, so S.unfoldr is just a while loop.
the function will be applied repeatedly as long as it returns Just, Then uses the next seed.

I like it, that's a cool way of solving the problem without making the function call itself (recursion)

Kenny
@Niall-Kenny
Does anyone know how I could get the following to type check in typescript?
async function getUserMedia(
  constraints: MediaConstraints
): Promise<Either<Number, MediaStream>> {
  try {
    const result = await navigator.mediaDevices.getUserMedia(constraints);
    return S.Right(result);
  } catch (error) {
    return S.Left<String>("1");
  }
}
Jon Ege Ronnenberg
@dotnetCarpenter

I have a problem that keep arising, namely I keep using free variables when composing functions.

//    get :: StrMap -> Future Object Error
const get = config => (
  F.attemptP (() => fetch (config.resource, config))
  .pipe (S.chain (S.ifElse (response => response.ok)
                           (F.encaseP (response => response.json ()))
                           (response => F.reject (new Error (`${response.status}: ${response.statusText}`)))))
)

//    resource :: String
const resource = 'https://nvdbapiles-v3...'

//    main :: Undefined -> Undefined
const main = () => {
  if (S.isJust (rowInsertionPoint)) {
    get ({resource, headers: {'Accept': 'application/vnd.vegvesen.nvdb-v3-rev1+json'}})
    .pipe (S.map (transformDataToHtmlOrError))
    .pipe (F.fork (renderError)
                  (renderSpeedBumps))
  } else {
    console.error ('Missing HTML tag <... id="rows" ...>')
  }
}

export default {
  main,
  onUnmounted () {},
  mountElement: document.getElementById ('page-overview')
}

In the above example resource and rowInsertionPoint is a free variable, where the former is a String and the latter is a Maybe HtmlElement.

  //    program :: List Campaign -> Promise
  const program = compose (
    find   (campaignName),
    goLive,
    insert (campaigns)
           (campaignName),
    saveToFile ('allCampaigns.json'),
  )

  program (campaigns)

Here, campaignName is a free variable.
My issue is that I find myself needing more arguments than one for my pure functions. This means that my function composition can not be constructed until run time.

Here is another example where yearField is global to the script:

//    groupCampaignsByYear :: Array Campaign -> Array (Pair String Number)
const groupCampaignsByYear = S.pipe ([
  S.sortBy (S.pipe ([S.prop (yearField), Descending])),
  S.groupBy (sameYear),
  // below might be unsafe...
  S.map (campaigns => S.Pair (getYearFromCampaign (campaigns[0]))
                             (S.size (campaigns))),
]);

I saw @avaq making some example with express where he passed an object around that had global variables to each URI end point. I'm not sure how well that works in non-expressjs code.

How do you deal with needing more and more arguments for monadic functions?

PS. Sorry if my type signatures are a little off.

Jon Ege Ronnenberg
@dotnetCarpenter
~my function composition can not be constructed until run time~ my function composition can not be constructed until call time
Jon Ege Ronnenberg
@dotnetCarpenter
For example in this case with campaignName:
//    main :: String -> Void
const main = campaignName => {

  //    program :: List Campaign -> Promise
  const program = compose (
    find   (campaignName),
    goLive,
    insert (campaigns)
           (campaignName),
    saveToFile ('allCampaigns.json'),
  )

  program (campaigns)
    .then (() => campaigns[campaignName].title)
    .then (console.log)    // we can create a terminal output with `console.log` (writing to stdout)
    .catch (console.error) // we write errors to stderr
}

campaigns is also a free variable.

//     campaigns :: List Campaign
import campaigns from '../allCampaigns.json' assert { type: 'json' }

campaigns is an Array StrMap