vlang / v

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. Supports automatic C => V translation. https://vlang.io
MIT License
35.82k stars 2.17k forks source link

Type-casting issue when providing a generic function as argument. #21132

Open debilosaurus opened 7 months ago

debilosaurus commented 7 months ago

Describe the bug

I am not 100% shure if this is bug, but I am not able to create a function with another function (from a std library) as argument. In my opinion this should work, but everything I tried didnt. Ths snipped is the most basic one. I tried with setting the generic to "f64" (the error message than refers surprisingly to the module like "math.f64"), or try to cast the function type (more errors of course).

I hope that I have made a mistake, but this type of functions would be very helpfull to avoid unnecessary (escecially for lazy people like me).

Reproduction Steps

Code: https://play.vlang.io/p/0a82f72d9f

// function-as-argument to reduce code

import math

type Vec5 = struct {
    x f64
    y f64
    z f64
    a f64
    b f64
}

fn from(s f64) Vec5 {
    return Vec5{s, s, s, s, s}
}

fn (v Vec5) abs_oldstyle() Vec5 {
    return Vec5{math.abs(v.x), math.abs(v.y), math.abs(v.z), math.abs(v.a), math.abs(v.b)}
}

fn (v Vec5) generic_new(f fn (f64) f64) Vec5 {
    return Vec5{f(v.x), f(v.y), f(v.z), f(v.a), f(v.b)}
}

fn main() {
    println('### start')

    mut v := from(-1)
    println(v)
    println(v.abs_oldstyle()) // ok
    // error: cannot use `fn (T) T` as `fn (f64) f64` in argument 1 to `Vec5.generic`
    println(v.generic_new(math.abs)) // error

    println('### end')
}

Expected Behavior

No error and the same result for "abs_oldstyle()" and "generic_new(math.abs)".

Current Behavior

Output:

code.v:32:23: error: cannot use `fn (T) T` as `fn (f64) f64` in argument 1 to `Vec5.generic_new`
   30 |     println(v.abs_oldstyle()) // ok
   31 |     // error: cannot use `fn (T) T` as `fn (f64) f64` in argument 1 to `Vec5.generic`
   32 |     println(v.generic_new(math.abs)) // error
      |                          ~~~~~~~~
   33 | 
   34 |     println('### end')
Exited with error status 1

Possible Solution

No response

Additional Information/Context

No response

V version

V 0.4.5 872bcbc

Environment details (OS name and version, etc.)

V full version: V 0.4.5 872bcbc
OS: linux, Debian GNU/Linux 11 (bullseye) (VM)
Processor: 1 cpus, 64bit, little endian, Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz

getwd: /home/admin/playground
vexe: /home/admin/v/v
vexe mtime: 2024-03-28 12:23:26

vroot: OK, value: /home/admin/v
VMODULES: OK, value: .vmodules
VTMP: OK, value: /tmp/v_0

Git version: git version 2.30.2
Git vroot status: Error: fatal: detected dubious ownership in repository at '/home/admin/v'
To add an exception for this directory, call:

    git config --global --add safe.directory /home/admin/v
.git/config present: true

CC version: cc (Debian 10.2.1-6) 10.2.1 20210110
thirdparty/tcc status: Error: fatal: detected dubious ownership in repository at '/home/admin/v/thirdparty/tcc'
To add an exception for this directory, call:

    git config --global --add safe.directory /home/admin/v/thirdparty/tcc
 Error: fatal: detected dubious ownership in repository at '/home/admin/v/thirdparty/tcc'
To add an exception for this directory, call:

    git config --global --add safe.directory /home/admin/v/thirdparty/tcc

[!NOTE] You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote. Other reactions and those to comments will not be taken into account.

debilosaurus commented 7 months ago

I have found a fix, but I am still thinking that some "autocast" feature dont work properly:

Change:

println(v.generic_new(math.abs)) // error

To:

math_abs := fn (s f64) f64 { return math.abs(s) }
println(v.generic_new(math_abs)) // fix with new function
debilosaurus commented 7 months ago

Again: This is no priority bug, maybe it isn't an issue at all. But here is another hint why I think that there must be casting issues; this time with an interface type.

Backround: For testing purposes with signed distance functions I wrote my own vector implementation. Then I tried to reduce the code to the absolute minimum - with interfaces . my goal is to generalize it in a simple way to more dimensions or other implementations (eg. array). But vlang sometimes is a beast ...

// vlang test, reduced for git:
// - vector operations (example) build with an interface
// - derive functions for a group of basic types
// questions:
// - how to use interface methods withou an additional cast
// - where are casts or generic types missing

module main

// scalar, basic type
type Scalar = f64

// basic function, not build as method, needed as arg
fn add_scalar(s1 Scalar, s2 Scalar) Scalar {
    return s1 + s2
}

// vector, interface an derived functions
interface Vector {
    libs (fn (Scalar, Scalar) Scalar) Scalar
//  lib2 (Vector, fn (Scalar, Scalar) Scalar) Vector
}

// derived method 1
fn (v Vector) sum() Scalar {
    return v.libs(add_scalar)
}

/* // derived method 2
fn (v1 Vector) add(v2 Vector) Vector {
    return v1.lib2(v2, add_scalar)
}
*/

// vec3, representation of the interface
type Vec3 = struct {
    x Scalar
    y Scalar
    z Scalar
}

// basic method a, safisfies the interface type
fn (v Vec3) libs(f fn (Scalar, Scalar) Scalar) Scalar {
    return f(v.x, f(v.y, v.z))
}

/* // interface method b, safisfies the interface type
fn (v1 Vec3) lib2(v2 Vec3, f fn (Scalar, Scalar) Scalar) Vec3 {
//fn (v1 Vec3) lib2(v2 Vector, f fn (Scalar, Scalar) Scalar) Vector {
    return Vec3{f(v1.x, v2.x), f(v1.y, v2.y), f(v1.z, v2.z)}
}
*/

fn main() {
    println('### start')

    // define a vector
    v := Vec3{1, 2, 3}

    // check if "Vec3" satifies the interface "Vector" -> ok
    println(Vector(v))

    // try method of type with fn as arg -> ok
    println(v.libs(add_scalar))

    // try methods of interface -> explicit cast needed, why?
//  println(v.sum()) // should work
    println(Vector(v).sum()) // but only works with a cast

    // try methods of interface -> error
    // - "Vec3" as args produce "unknown method Vec3.sum/add"
    // - "Vector" as args produce "no field name x/y/z" 
//  println(v.add(v))

    // the goal, concat
//  println(v.add(v).add(v))

    println('### end')
}
debilosaurus commented 1 week ago

After some time spending to other things, I tried to find the cause of this behavior. I hope this helps to improve your language.

// vlang test, reduced for git:
// - vector operations (example) build with an interface
// - derive functions for a group of basic types
// questions/problems:
// - the compiler assumes initially that a type dont satisfies an interface,
//   and so he cant solve references to the type itself
// - interface methods should work without an additional cast

module main

// scalar, basic type
type Scalar = f64

// basic function, not build as method, needed as arg
fn add_scalar(s1 Scalar, s2 Scalar) Scalar {
    return s1 + s2
}

// vector, interface and derived functions
interface Vector {
    elem (int) Scalar // dependency of an additional method
    libs (fn (Scalar, Scalar) Scalar) Scalar
    lib2 (Vector, fn (Scalar, Scalar) Scalar) Vector
}

// derived method 1
fn (v Vector) sum() Scalar {
    return v.libs(add_scalar)
}

// derived method 2
fn (v1 Vector) add(v2 Vector) Vector {
    return v1.lib2(v2, add_scalar)
}

// vec3, representation of the interface
//type Vec3 = struct { // syntax change?
struct Vec3 {
    x Scalar
    y Scalar
    z Scalar
}

// problem 1: additional method needed, because v assumes a "not-satisfying"
fn (v Vec3) elem(i int) Scalar {
   if i == 1 { return v.x }
   if i == 2 { return v.y }
   if i == 3 { return v.z }
   return Scalar(0.0)
}
// basic method a, safisfies the interface type
fn (v Vec3) libs(f fn (Scalar, Scalar) Scalar) Scalar {
    return f(v.x, f(v.y, v.z))
}

// interface method b, safisfies the interface type
//fn (v1 Vec3) lib2(v2 Vec3, f fn (Scalar, Scalar) Scalar) Vec3 {
//fn (v1 Vec3) lib2(v2 Vec3, f fn (Scalar, Scalar) Scalar) Vector {
fn (v1 Vec3) lib2(v2 Vector, f fn (Scalar, Scalar) Scalar) Vector {
    // this failes, because it is assumed that it does NOT satisfies the interface
    //return Vec3{f(v1.x, v2.x), f(v1.y, v2.y), f(v1.z, v2.z)}
    // so a function is needed, even if we know how to address the elements for this type
    return Vec3{f(v1.elem(1), v2.elem(1)), f(v1.elem(2), v2.elem(2)), f(v1.elem(3), v2.elem(3))}
}

fn main() {
    println('### start')

    // define a vector
    v := Vec3{1, 2, 3}

    // check if "Vec3" satifies the interface "Vector" -> ok
    println(Vector(v))

    // try method of type with fn as arg -> ok
    println(v.libs(add_scalar))

    // try methods of interface -> explicit cast needed, why?
//  println(v.sum()) // should work
    println(Vector(v).sum()) // problem 2: only works with a cast

    // try methods of interface -> error
    // - "Vec3" as args produce "unknown method Vec3.sum/add"
    // - "Vector" as args produce "no field name x/y/z" 
//  println(v.add(v)) // should work
    println(Vector(v).add(v)) // problem 2: only works with a cast

    // the goal, concat/mix own/derived methods
//  println(v.add(v).add(v)) // should work
    println(Vector(v).add(v).add(v)) // problem 2: only works with a cast

    println('### end')
}