Integrated Dynamics

Integrated Dynamics

63M Downloads

Pipe operator handles multi-parameter operators strangely

aadams1993 opened this issue ยท 5 comments

commented

Issue type:

  • ๐Ÿ› Bug

Short description:

Using the pipe operator with 2 operators that both take multiple arguments gives an operator that takes more arguments than it should. For example the list contans operator paired with map:
contains has type List -> Any -> Boolean
map has type Operator -> List -> List
composing them as Pipe(contains, map) has type List -> Any -> List -> List,
with the "Any" being the extra argument; what exactly is map's operator here? The intent was to have the result of passing one argument to contains (giving an operator with type Any -> Bool) serve as that operator. Instead, it seems to be getting a Boolean after the second argument is passed to contains which makes it give a type error. This behaviour isn't specific to contains and map though.

The idea here was to make an operator that could be applied to 2 arbitrary lists to determine if the elements in the second list were present in the first; I was trying to use this as a building block to matching recipes based on the list of items sitting in front of an entity reader for some in-world autocrafting.

Steps to reproduce the problem:

  1. Make an operator variable card for the List contains operator
  2. Make an operator variable card for the map operator
  3. Make a pipe variable card with the contains operator as the first argument and the map operator as the second argument
  4. Put both the contains and map operator cards in a variable store
  5. Connect a display panel to the variable sotre
  6. Put the pipe variable card in the display panel to show its type
  7. You should see something like this:
    image

Expected behaviour:

In the above example, Pipe(contains, map) should give an operator with type List -> List -> List. This is the behaviour of the equivalent Haskell code, a language which the mod has clearly taken a great deal of inspiration from:

elem :: a -> [a] -> Bool
map :: (a -> b) -> [a] -> [b]
(.) :: (b -> c) -> (a -> b) -> a -> c
flip :: (a -> b -> c) -> b -> a -> c

flip elem :: [a] -> a -> Bool -- equivalent to [a] -> (a -> Bool), so b in (.)'s type corresponds to (a -> Bool)
test = map . (flip elem) :: [a] -> [a] -> [Bool] -- Note haskell's (.) operator takes its parameters in reverse order to ID's pipe operator
-- test [2,3,4] [1,2,3,4,5,6,7] = [False, True, True, True, False, False, False]


Versions:

  • This mod: 0.11.12
  • Minecraft: 1.12.2
  • Forge: 14.23.4.2703
commented

Something does indeed seem to be going wrong there, will look into it.

commented

Sorry for the long delay, but this has been fixed now and will be part of the next release.

commented

I'm not seeing how this is a bug. The Pipe operator seems to works like documented: it pipes the output of the Contains operator (a boolean) into the Map operator.

Here's a way to make an operator that does what you want:

  1. Make a variable with the Apply operator (A)
  2. Make a variable with the Contains operator (B)
  3. Make an Apply variable with the arguments A and B (C)
  4. Make a variable with the Map operator (D)
  5. Make a Pipe variable with the arguments C and D
commented

The issue is the pipe operator doesn't consider currying, while several others work just fine with it - notably apply in your workaround; contains is a binary operator, which would normally require apply2 in the absense of currying.

With currying, if you have a function (or operator in ID parlance) with multiple arguments, that's the same as a function that takes a single argument and returns another function which takes the next argument and so on. So, contains(xs, x) is really (contains(xs))(x), and contains(xs) is a function that determines if the argument given to it (x in this case) is in the list xs.

For the pipe operator that should translate to passing the first input of the overall operation to contains, then (because of currying) it passes the output of that (a function) to the first input of map (which is expected to be a function), and subsequent inputs would be passed directly to map. Instead, what we get right now is an operation the mod considers valid (in that you can construct a variable card for it), but doesn't make any sense conceptually and spits out an error when you actually try to use it, because of course the parameters are all wrong.

I hope this clears things up; I know this stuff is complicated to get your head around.

commented

I am not that familiar with functional programming, but your explanation is clear to me. Thanks!