(Ep1) Mandala Maker in JavaScript (functional style)

Поделиться
HTML-код
  • Опубликовано: 2 июн 2024
  • Let's build a mandala maker in JavaScript and HTML canvas, using concepts from functional programming 😊😋
    ⭐️ Support the channel on Patreon:
    / christopherokhravi
    Links mentioned in this video:
    - 24ways.org/2018/the-art-of-ma...
    - 24ways.org/

Комментарии • 14

  • @ebrelus7687
    @ebrelus7687 Год назад

    Awesome project

  • @benoitgrelard
    @benoitgrelard 5 лет назад

    I think `R.juxt` is the ramda function you were after at the end, regarding how to apply a list of transformation functions to a value. Love this new series by the way!

  • @lunaArtemisOld
    @lunaArtemisOld 5 лет назад +2

    Great video!
    The function you were looking for is called juxt in ramda. ramdajs.com/docs/#juxt
    A thought about the transformations:
    I think the transformations array can be optimized in a few ways here. I dont think the array you have built up will work for all points on the board (specifically if one of the numbers is negative and the other is not). I thought about it for a while, and discovered that all the points can be expressed by 0 to 3 of these functions: xReflect, yReflect and invert (invert is actually just a reflection around the line that forms when you enter the function f(x) = x in a graphing calculator). Let's say you start with (3,1). Then you do invert and you have (3,1) and (1,3). If you do xReflect on both of those, you will get (3,-1) and (1,-3), leaving us with the 4 points [ (3,1), (1,3), (3,-1), (1,-3) ]. Keeping these four, while doing yReflect on them and creating a copy, leaves us with these 8 points: [ (3,1), (1,3), (3,-1), (1,-3), (-3,1), (-1,3), (-3,-1), (-1,-3) ]. These are the 8 points we need, and plugging in any of these 8 points into our new transform function will give us these. (You can try doing this on your drawing, picking a point, doing invert (while keeping track of the starting dot), then the other functions, and see that it works for any starting point.) So, how would we define this list of transformations? A simple approach would be this:
    const transformations = [
    pipe(id, pipe(id, id )),
    pipe(id, pipe(id, yReflect)),
    pipe(id, pipe(xReflect, id )),
    pipe(id, pipe(xReflect, yReflect)),
    pipe(invert, pipe(id, id )),
    pipe(invert, pipe(id, yReflect)),
    pipe(invert, pipe(xReflect, id )),
    pipe(invert, pipe(xReflect, yReflect))
    ]; //why can't youtube have monospace fonts :(
    which can be simplified to:
    const transformations = [
    id,
    yReflect,
    xReflect,
    pipe(xReflect, yReflect),
    invert,
    pipe(invert, yReflect),
    pipe(invert, xReflect),
    pipe(invert, pipe(xReflect, yReflect))
    ];
    This works fine, and it's what i would recommend you use in the series (for simplicity), but i would personally do something else, which is this:
    (i included this because i thought it was interesting)
    I would define each of the functions as array-returning functions, with the returned arrays containing the original value and the modified value. This can be done with a "chainify" function (you'll see why it's called that later).
    const chainify = f => x => [x, f(x)];
    Here are the main transformation functions (i've changed them slightly, but they do the same thing as before):
    const invert = ({x, y}) => ({ x: y, y: x });
    const refx = ({x, y}) => ({ x: x, y: -y });
    const refy = ({x, y}) => ({ x: -x, y: y });
    Using chainify, they can be defined like this:
    const invert = chainify(({x, y}) => ({ x: y, y: x }));
    const refx = chainify(({x, y}) => ({ x: x, y: -y }));
    const refy = chainify(({x, y}) => ({ x: -x, y: y }));
    Now, if we implement chain for arrays according to the fantasyland specification, it looks like this:
    the fantasyland type signature for chain is:
    chain :: Chain m => m a ~> (a -> m b) -> m b
    for arrays (and made a curried function as opposed to a method on the Array class), this would be:
    chain :: (a -> [b]) -> [a] -> [b]
    First though, we need some other functions:
    //dropFst is just a function that removes the first element of an array
    const pipe = (...fns) => x => fns.reduce((acc, f) => f(acc), x); //a pipe function that takes any amount of functions
    const map = f => xs => xs.map(f) //curried map
    const reduce = f => acc => xs => xs.reduce((acc, x) => f(acc)(x), acc); // curried reduce
    const concat = xs => ys => xs.concat(ys); //curried concat
    const chain = f => pipe(map(f), reduce(concat)([]));
    If it's unclear (it probably is if you're unfamiliar), chain can be used like this:
    chain( x => [x,"hi"] )( [1,2,3] ); //=>[1, "hi", 2, "hi", 3, "hi" ]
    More info on chain:
    ramdajs.com/docs/#chain
    github.com/fantasyland/fantasy-land#chain
    now, we can simply define pattern like this:
    const pattern = pipe(
    invert,
    chain(refx),
    chain(refy)
    ); //no long transformations array
    Additionally, if we use pipeK, it's as simple as:
    const pattern = pipeK(invert, refx, refy);
    There's more info on pipeK here: ramdajs.com/docs/#pipeK
    I've defined it like this:
    const pipeWith = pf => (...fns) => pipe(...fns.map(f => pf(f)));
    const pipeC = pipeWith(chain);
    const pipeK = (...fns) => pipe(fns[0], pipeC(...dropFst(fns)));

  • @winnerkrikchai8685
    @winnerkrikchai8685 5 лет назад

    You can combine those transformations with ‘ap’ function in Ramda.
    In Haskell it could be written like this
    transformations p =
    [id, invert, xReflect . invert, xReflect, reflect, invert . yReflect . xReflect, yReflect . invert, yReflect] pure p

  • @joorce
    @joorce 5 лет назад

    Nice video.
    Not trying to evangelize here but, have you thought about using VSCode? I see that your using vim so you could use the vscodevim and feel at home.
    Pair that with the Quokka plugin that shows the result of function calling inline. I think is a wonderful utility for coding and maybe even better for a tutorial/hacking session like this.
    Anyway, good content in your channel. I like specially this kind of videos where you see the mental process behind the code.

  • @jameshibbs6506
    @jameshibbs6506 5 лет назад

    Shouldn't you be able to invert and xTranslate both points in that quadrant then yTranslate the 4 points of the two quadrants just created, each time creating an object of all the points at that step.. By the way, nice tutorial, I like the energy and showing the thought process while trying to solve a problem. Keep up the good work.

  • @rolfnoduk
    @rolfnoduk 5 лет назад +1

    Reflect in y=x line ;-)

  • @Baroquepassion
    @Baroquepassion 3 года назад

    the maths is much easier if you use polar coordinates

  • @Tymon0000
    @Tymon0000 5 лет назад +1

    Your piping is confusing for me, as I am used to:
    someObject
    .pipe(something)
    .pipe(anotherThing)
    .pipe(somethingElse)

    • @cprn.
      @cprn. 5 лет назад +1

      What you're used to is calling a method of an object, what he does is calling a globally scoped function. There are pros and cons for both. To be honest I never fully understood the idea of `pipe()` as a method. It's nice to chain calls but this way I find cumbersome. It forces me to return a "pipable" type (i.e. an object that has the `pipe()` member). With functional piping I can chain all possible functions without the expectation of the return value to be a duck.

    • @ChristopherOkhravi
      @ChristopherOkhravi  5 лет назад +2

      ​Great explanation​ @@cprn.. Just to add some more to it: It seems to me that the benefit of pipe as a function (as opposed to as a method) is partial application. In other words, you can compose functions (i.e. prepare them) before you have the data. Tymski, I'd highly recommend the Ramda episodes 1, 27, and 19 (linked below) on my channel if you want to learn more about what we informally here referred to as function syntax as opposed to method syntax.
      ruclips.net/p/PLrhzvIcii6GMeyUfpn-o5xVCH3_UykrzI

  • @enihar
    @enihar 5 лет назад

    They are Hindu religious structures.

    • @ChristopherOkhravi
      @ChristopherOkhravi  5 лет назад

      Thanks for clarifying! I hope no one felt that I was being too insensitive. Thanks for watching! 😊

    • @enihar
      @enihar 5 лет назад

      @@ChristopherOkhravi Hindus are very liberal in these matters. In Indian itself, we have dozens of languages pronouncing each word in their way :) BTW I am a huge fan of your channel specially design pattern series. Noone else explained it better as you have done. Hats off and thank you.