How to define a function type with two possibly distinct comparable parameter types?

Hello,

in a definition like this

    func : comparable -> comparable -> comparable

how do I express that the first two arguments are of possibly different types?

    func : a -> b -> c

loses the “comparable” information.

    func: comparable -> comparable -> comparable

seems like it would restrain the function to take two arguments of same type.

What do I do?

1 Like

I think you can do something like comparableA -> comparableB -> comparableC. I’m curious as to what you’re planning on comparing that could be different though.

1 Like

Thanks.
I’m still working with my idiotic datastructure for dates (first level for years, second level for months, third level for days, fourth level for names, ok it would be simpler to do a single level and index simply with dates, but at least that’s a good exercise. And I want to be able to easily regroup the items by years or months so at least that will be easy.)

{
    2021: {
        8: {
            31: {"Jeremy": 1}
        },
        9: {
            7: {"Jane": 5},
            10: {"Sandra": 5},
            12: {"Michel": 2, "Pedro": 2}
        }
    }
}


type alias Achievements =
    Dict Int (Dict Int (Dict Int (Dict String Int)))

To be able to iterate over the dates in order I wrote a function that can turn a dict of dicts into a dict indexed by pairs of the keys.

    shallowDict : Dict comparable (Dict comparable1 c) -> Dict ( comparable, comparable1 ) c
    shallowDict dct =
        dct
            |> Dict.toList
            |> List.map
                (\( kA, subDict ) ->
                    ( kA, subDict |> Dict.toList )
                )
            |> List.map
                (\( kA, subList ) ->
                    subList |> List.map (\( kB, vC ) -> ( ( kA, kB ), vC ))
                )
            |> List.concat
            |> Dict.fromList


    indexedMap : (Int -> Int -> Int -> String -> Int -> a) -> Achievements -> List a
    indexedMap func achievements =
        achievements
            |> shallowDict
            |> shallowDict
            |> shallowDict
            |> Dict.toList
            |> List.map
                (\( ( ( ( year, month ), day ), name ), countValue ) ->
                    func year month day name countValue
                )
1 Like

Wouldn’t it be more idiomatic Elm to use custom data types that describe your application structure precisely?

Something along the lines of


type alias Score = Int
type alias User = String
type alias PerUserScore = (User, Score)

-- Three-level grouping hierarchy
-- This could be a separate module with appropriate
-- transformation functions
type Day a = Day Int (List a) 
type Month a = Month Int (List (Day a)) 
type Year a = Year Int (List (Month a))

-- Then the equivalent of your nested-dict Achievements alias is
type Achievements a = List (Year a)

-- For different flat groupings you can introduce new types:

type DMY = DMY Int Int Int
type FlatGrouping = Daily DMY | YearMonth Int Int | Yearly Int

type PerUserAchievements = All User (List (DMY, Score))
                         | Grouped User (List (FlatGrouping, (List Score)))


-- Or as a record:
type alias UserAchievements =
    { user : User
      -- all of user's achievements sorted by date
    , achievements : List (DMY, Score)
      -- or grouped yearly for this user by using JustsScore leaves
    , yearly : Achievements Score
    }

-- Set of functions to go with the module
fromJson : Json.Value -> LeafDecoder a -> Achievements a

monthlyForUser : Achievements PerUserScore -> User -> PerUserAchievements
...

Obviously this is just off-the-cuff without any thought put into actual application. You’d probably spend time designing your data structures with care and with your concrete application in view.

Stick that in a module, and design a set of functions to deal with setting, getting, sorting, transforming, serialization, validation to ensure dates are correct for different months, etc. Ideally, you’d design this in a way that prevents impossible states, which is not necessarily the case in this quick example.

You can go more generic, if so inclined, by designing a separate module for generic year/month/day hierarchy manipulation, etc.

The point is that you get rid of the question completely through shaping your application with data types, instead of suffering with layers of Dict (Dict (Dict ...))).

Not the answer you were looking for, but hope it still helps.

A function that takes comparables must take two arguments of the same type, otherwise they can’t be compared.

Would type aliases work for your scenario?

In your example, you can add a bunch of type aliases for all key types you intend to use:

type alias Year = Int
type alias Month = Int
type alias YearMonth = String

Then you can differentiate keys easily:

func : Year -> Month -> YearMonth

Might help add some type safety to the existing design:

type alias Achievements =
    Dict Year (Dict Month (...